diff --git a/.devcontainer/api/devcontainer.json b/.devcontainer/api/devcontainer.json new file mode 100644 index 0000000000..f1a0fef129 --- /dev/null +++ b/.devcontainer/api/devcontainer.json @@ -0,0 +1,85 @@ +{ + "name": "Tracker API", + "image": "mcr.microsoft.com/devcontainers/javascript-node:20", + + "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/tracker,type=bind,consistency=cached", + "workspaceFolder": "/workspaces/tracker/api", + + "mounts": ["source=${localWorkspaceFolder},target=/workspaces/tracker,type=bind,consistency=cached"], + + "containerEnv": { + "NODE_ENV": "development", + "DB_URL": "http://localhost:8529", + "DB_NAME": "track_dmarc", + "DB_PASS": "test", + "NATS_URL": "nats://localhost:4222", + "NODE_OPTIONS": "--dns-result-order=ipv4first", + "AUTH_TOKEN_EXPIRY": "1d", + "REFRESH_TOKEN_EXPIRY": "30d", + "SIGN_IN_KEY": "devcontainer-sign-in-key-not-for-production", + "REFRESH_KEY": "devcontainer-refresh-key-not-for-production", + "AUTHENTICATED_KEY": "devcontainer-auth-key-not-for-production", + "CIPHER_KEY": "devcontainer-cipher-key-not-for-prod", + "HASHING_SALT": "12", + "LOGIN_REQUIRED": "false", + "DEPTH_LIMIT": "10", + "COST_LIMIT": "5000", + "SCALAR_COST": "1", + "OBJECT_COST": "1", + "LIST_FACTOR": "10", + "TRACING_ENABLED": "false", + "NOTIFICATION_API_KEY": "devcontainer-notification-api-key", + "NOTIFICATION_API_URL": "http://localhost:9999", + "NOTIFICATION_AUTHENTICATE_EMAIL_ID": "devcontainer-authenticate-email-id", + "NOTIFICATION_AUTHENTICATE_TEXT_ID": "devcontainer-authenticate-text-id", + "NOTIFICATION_ORG_INVITE_BILINGUAL": "devcontainer-org-invite-bilingual-id", + "NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL": "devcontainer-org-invite-create-account-bilingual-id", + "NOTIFICATION_ORG_INVITE_REQUEST_BILINGUAL": "devcontainer-org-invite-request-bilingual-id", + "NOTIFICATION_PASSWORD_RESET_BILINGUAL": "devcontainer-password-reset-bilingual-id", + "NOTIFICATION_TWO_FACTOR_CODE_EN": "devcontainer-two-factor-code-en-id", + "NOTIFICATION_TWO_FACTOR_CODE_FR": "devcontainer-two-factor-code-fr-id", + "NOTIFICATION_VERIFICATION_EMAIL_BILINGUAL": "devcontainer-verification-email-bilingual-id", + "SERVICE_ACCOUNT_EMAIL": "dev@example.com" + }, + + "forwardPorts": [4000, 8529, 4222], + "portsAttributes": { + "4000": { + "label": "GraphQL API", + "onAutoForward": "notify" + }, + "8529": { + "label": "ArangoDB Web UI" + }, + "4222": { + "label": "NATS" + } + }, + + "postCreateCommand": "npm install && [ ! -f .env ] && cp .env.example .env || true", + + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "moby": false + } + }, + + "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/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..596e4131f6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,190 +1,383 @@ 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + uptime + 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 + + - 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 + + - 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: "/services/uptime" + schedule: + interval: weekly + day: sunday + time: "11:00" + open-pull-requests-limit: 3 + cooldown: + default-days: 7 + + - package-ecosystem: pip + directory: "/services/uptime" + 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 + + - 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 + + - 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 + + - 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 e414847f4b..d45e26e47f 100644 --- a/Makefile +++ b/Makefile @@ -16,12 +16,25 @@ endef .PHONY: cluster cluster: - gcloud beta container --project "$(project)" clusters create "$(name)" --region "$(region)" --no-enable-basic-auth --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: - kustomize build platform/creds/${mode} | kubectl apply -f - - kustomize build app/creds/${mode} | kubectl apply -f - + 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: @@ -31,38 +44,55 @@ 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: - kustomize build scanners/domain-dispatcher | kubectl apply -f - + kubectl delete job domain-dispatcher-manual -n scanners --ignore-not-found && + kubectl create job domain-dispatcher-manual --from=cronjob/domain-dispatcher -n scanners + +.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: @@ -70,11 +100,12 @@ scanners: .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: @@ -82,87 +113,80 @@ guidance: .PHONY: summaries summaries: - kubectl apply -f services/summaries/summaries-job.yaml + 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 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 + COST_LIMIT=75000 + DB_NAME=track_dmarc + DB_PASS=test + DB_URL=http://arangodb.db:8529 + DEPTH_LIMIT=15 + HASHING_SALT=somerandomvalue + LIST_FACTOR=1 LOGIN_REQUIRED=true + NATS_URL=nats://nats:4222 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 - 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 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 - HASHING_SALT=somerandomvalue 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 @@ -187,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 5a18517078..729dd1a9d0 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,12 @@ As is common with microservices projects, this repository is organized in the [m ``` . ├── api -├── app ├── 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](api/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/.env.example b/api/.env.example index 045f8e4da1..d55a990ee9 100644 --- a/api/.env.example +++ b/api/.env.example @@ -1,44 +1,30 @@ -DB_PASS= -DB_URL= -DB_NAME= AUTHENTICATED_KEY= AUTH_TOKEN_EXPIRY= -REFRESH_TOKEN_EXPIRY= -SIGN_IN_KEY= -REFRESH_KEY= +CIPHER_KEY= +COST_LIMIT= +DB_NAME= +DB_PASS= +DB_URL= +DEPTH_LIMIT= +HASHING_SALT= +LIST_FACTOR= LOGIN_REQUIRED= -NOTIFICATION_API_URL= +NATS_URL= NOTIFICATION_API_KEY= +NOTIFICATION_API_URL= 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_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_EN= -NOTIFICATION_VERIFICATION_EMAIL_FR= -DMARC_REPORT_API_URL= -DMARC_REPORT_API_TOKEN= -DMARC_REPORT_API_SECRET= -DEPTH_LIMIT= -COST_LIMIT= -SCALAR_COST= +NOTIFICATION_VERIFICATION_EMAIL_BILINGUAL= OBJECT_COST= -LIST_FACTOR= -CIPHER_KEY= -DNS_SCANNER_ENDPOINT= -HTTPS_SCANNER_ENDPOINT= -SSL_SCANNER_ENDPOINT= +REFRESH_KEY= +REFRESH_TOKEN_EXPIRY= +SCALAR_COST= +SERVICE_ACCOUNT_EMAIL= +SIGN_IN_KEY= TRACING_ENABLED= -REDIS_PORT_NUMBER= -REDIS_DOMAIN_NAME= -DKIM_SCAN_CHANNEL= -DMARC_SCAN_CHANNEL= -HTTPS_SCAN_CHANNEL= -SPF_SCAN_CHANNEL= -SSL_SCAN_CHANNEL= -HASHING_SALT= diff --git a/api/Dockerfile b/api/Dockerfile index 5e778fc1ef..aacb3cd5f2 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,7 +1,7 @@ # 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 +FROM node:20.20.0-alpine3.22 AS base WORKDIR /app @@ -9,11 +9,11 @@ FROM base AS builder COPY package*.json .babelrc ./ -RUN npm install +RUN npm ci COPY ./src ./src COPY ./index.js . -COPY ./database.json . +COPY ./lingui.config.js . COPY ./.env.example . RUN npm run build @@ -29,7 +29,7 @@ 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.json . +COPY --from=builder /app/lingui.config.js . COPY --from=builder /app/dist ./dist USER node diff --git a/api/README.md b/api/README.md index b707c7747c..d676dd84e2 100644 --- a/api/README.md +++ b/api/README.md @@ -11,7 +11,7 @@ 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. +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 @@ -28,38 +28,24 @@ 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=15 COST_LIMIT=10000 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 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 ``` @@ -90,7 +76,7 @@ npm run dev #### 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. +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: @@ -122,38 +108,24 @@ 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 +DEPTH_LIMIT=15 COST_LIMIT=10000 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 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 ``` diff --git a/api/cloudbuild.yaml b/api/cloudbuild.yaml index ecbb3cd783..c3ee4b9bf0 100644 --- a/api/cloudbuild.yaml +++ b/api/cloudbuild.yaml @@ -1,131 +1,142 @@ 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: '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: mikewilliamson/wait-for + id: wait_arango + args: ['arangodb:8529'] -- name: node:alpine - id: install - dir: api - entrypoint: npm - args: ['ci', '--no-optional'] + - name: node:20-alpine + id: install + dir: api + entrypoint: npm + args: ['ci'] -- name: node:alpine - id: lint - dir: api - entrypoint: npm - args: ['run', lint] + - name: node:20-alpine + id: lint + dir: api + entrypoint: npm + args: ['run', lint] -- name: node: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_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 - - HASHING_SALT=$_HASHING_SALT - - LOGIN_REQUIRED=false + - name: node:20-alpine + id: lingui-extract + dir: api + entrypoint: ash + args: + - '-c' + - | + npm run extract | tee /workspace/lingui-extract-output.txt -- name: node:alpine - id: lingui-extract - dir: api - entrypoint: npm - args: ['run', 'extract'] + - 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 ) -- name: node:alpine - id: lingui-compile - dir: api - entrypoint: npm - args: ['run', 'compile'] + # 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: 'gcr.io/cloud-builders/docker' - id: generate-image-name - entrypoint: 'bash' - dir: api - args: - - '-c' - - | - echo "gcr.io/$PROJECT_ID/api-js:$BRANCH_NAME-$SHORT_SHA-$(date +%s)" > /workspace/imagename + - name: node:20-alpine + id: lingui-compile + dir: api + entrypoint: npm + args: ['run', 'compile'] -- name: 'gcr.io/cloud-builders/docker' - id: build-if-master - entrypoint: 'bash' - dir: api - args: - - '-c' - - | - if [[ "$BRANCH_NAME" == "master" ]] - then + - 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 . - 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 + - 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: diff --git a/api/database.json b/api/database.json deleted file mode 100644 index c90600119e..0000000000 --- a/api/database.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "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": "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/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..32f30d91f0 --- /dev/null +++ b/api/docker-compose.yaml @@ -0,0 +1,78 @@ +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:-arangodb-data}:/var/lib/arangodb3 + +volumes: + arangodb-data: diff --git a/api/index.js b/api/index.js index 9ad91192e2..89ba00fc90 100644 --- a/api/index.js +++ b/api/index.js @@ -3,6 +3,8 @@ 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, @@ -17,41 +19,9 @@ const { TRACING_ENABLED: tracing, HASHING_SALT, LOGIN_REQUIRED = 'true', + NATS_URL, } = process.env -const collections = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - ;(async () => { const db = new Database({ url, @@ -69,6 +39,18 @@ const 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) { @@ -83,10 +65,13 @@ const collections = [ 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, @@ -100,13 +85,10 @@ const collections = [ tracing, }) - console.log( - `Starting server with "LOGIN_REQUIRED" set to "${LOGIN_REQUIRED}"`, - ) + 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`) - console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}/graphql`) }) })() diff --git a/api/jest.config.js b/api/jest.config.js index f76d1efeb1..1aeda50c87 100644 --- a/api/jest.config.js +++ b/api/jest.config.js @@ -11,5 +11,8 @@ module.exports = { 'index.js', 'env.js', ], + moduleNameMapper: { + '^axios$': require.resolve('axios'), + }, testTimeout: 120000, } diff --git a/api/lingui.config.js b/api/lingui.config.js index bd5edec8f7..1f1b28e62f 100644 --- a/api/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 index f55a977fe5..9ac7bc81da 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -9,70 +9,334 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@lingui/core": "^3.13.0", - "apollo-server": "^3.6.3", - "apollo-server-core": "^3.6.3", - "apollo-server-express": "^3.6.3", + "@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": "^7.6.1", + "arangojs": "^10.2.2", "bcryptjs": "^2.4.3", - "cookie-parser": "^1.4.6", + "body-parser": "^1.20.5", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dataloader": "^2.0.0", "dotenv-safe": "^8.2.0", - "express": "^4.17.2", + "express": "^4.22.2", "express-request-language": "^1.1.15", - "graphql": "^15.8.0", + "graphql": "^16.12.0", "graphql-depth-limit": "^1.1.0", - "graphql-playground-middleware-express": "^1.7.23", - "graphql-redis-subscriptions": "^2.4.2", - "graphql-relay": "^0.9.0", - "graphql-scalars": "^1.14.1", - "graphql-subscriptions": "^1.2.1", + "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", - "graphql-voyager": "^1.0.0-rc.31", "ioredis": "^4.28.3", "isomorphic-fetch": "^3.0.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.2", "make-plural": "^7.1.0", - "moment": "^2.29.1", - "notifications-node-client": "^5.1.0", - "subscriptions-transport-ws": "^0.11.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.7.0" + "uuid": "^11.1.1", + "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": "^27.4.6", - "@lingui/cli": "^3.13.0", - "@lingui/loader": "^3.13.0", - "@lingui/macro": "^3.13.0", + "@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": "^27.4.6", + "babel-jest": "^30.3.0", "babel-plugin-macros": "^3.1.0", "babel-polyfill": "^6.26.0", - "eslint": "^7.32.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", + "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^24.7.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.2.0", - "jest": "^27.4.7", + "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": "^27.4.6", + "jest-matcher-utils": "^30.3.0", + "nodemon": "^3.1.11", "prettier": "^2.5.1", - "supertest": "^6.2.1" + "supertest": "^7.0.0" } }, - "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==", + "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/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", @@ -86,7 +350,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": { @@ -94,26 +357,88 @@ "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/@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/@apollographql/apollo-tools": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.2.tgz", - "integrity": "sha512-KxZiw0Us3k1d0YkJDhOpVH5rJ+mBfjXcgoRoCcslbgirjgLotKMzOcx4PZ7YTEvvEROmvG7X3Aon41GvMmyGsw==", + "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": ">=8", - "npm": ">=6" + "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/@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==", + "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": { - "xss": "^1.0.8" + "@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": { @@ -146,47 +471,49 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "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/highlight": "^7.16.7" + "@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.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "convert-source-map": "^1.7.0", + "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.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -196,15 +523,23 @@ "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.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "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/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@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" @@ -236,23 +571,36 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "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.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" + "@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" - }, - "peerDependencies": { - "@babel/core": "^7.0.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", @@ -310,13 +658,10 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "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, - "dependencies": { - "@babel/types": "^7.16.7" - }, "engines": { "node": ">=6.9.0" } @@ -334,39 +679,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "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/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, "engines": { "node": ">=6.9.0" } @@ -384,34 +713,33 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "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/types": "^7.16.7" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "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-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@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": { @@ -427,9 +755,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "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" @@ -490,30 +818,39 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "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.16.7" + "@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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "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" @@ -535,37 +872,22 @@ } }, "node_modules/@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", + "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.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "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==", + "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", @@ -586,10 +908,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", + "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" }, @@ -952,6 +1277,21 @@ "@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", @@ -977,12 +1317,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.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.14.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1094,12 +1434,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1367,16 +1707,15 @@ } }, "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==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "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" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1738,59 +2077,62 @@ } }, "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==", + "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/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.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "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.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@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.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", + "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.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" + "@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.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "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-validator-identifier": "^7.16.7", - "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" @@ -1802,801 +2144,806 @@ "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==", + "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": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "cosmiconfig": ">=6" + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "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": { - "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" + "tslib": "^2.4.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==", + "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": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "tslib": "^2.4.0" } }, - "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==", + "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": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "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/@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/@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/@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/@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/@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/@graphql-tools/merge": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.3.tgz", - "integrity": "sha512-XCSmL6/Xg8259OTWNp69B57CPWiVL69kB7pposFrufG/zaAlI9BS68dgzrxmmSqZV5ZHU4r/6Tbf6fwnEJGiSw==", - "dependencies": { - "@graphql-tools/utils": "^8.6.2", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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/@graphql-tools/mock": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.5.2.tgz", - "integrity": "sha512-5BosbTWkzo5tdxIqoqokGLDPmdTS1tE4QNm6a2ONlXz0MaynPRAQ8b2CcSy/c6r0lDmCdkLtbVrRtV6m/wE6Kw==", - "dependencies": { - "@graphql-tools/schema": "^8.3.2", - "@graphql-tools/utils": "^8.6.2", - "fast-json-stable-stringify": "^2.1.0", - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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/@graphql-tools/schema": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.2.tgz", - "integrity": "sha512-77feSmIuHdoxMXRbRyxE8rEziKesd/AcqKV6fmxe7Zt+PgIQITxNDew2XJJg7qFTMNM43W77Ia6njUSBxNOkwg==", - "dependencies": { - "@graphql-tools/merge": "^8.2.3", - "@graphql-tools/utils": "^8.6.2", - "tslib": "~2.3.0", - "value-or-promise": "1.0.11" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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/@graphql-tools/utils": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.2.tgz", - "integrity": "sha512-x1DG0cJgpJtImUlNE780B/dfp8pxvVxOD6UeykFH5rHes26S4kGokbgU8F1IgrJ1vAPm/OVBHtd2kicTsPfwdA==", - "dependencies": { - "tslib": "~2.3.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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/@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==", + "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": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "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==", + "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": { - "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/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/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": ">=8" + "node": ">=18" } }, - "node_modules/@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", + "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": { - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", - "slash": "^3.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "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/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, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "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/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": { - "color-name": "~1.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "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/@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/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": ">=8" + "node": ">=18" } }, - "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==", + "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/@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/@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, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/core": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", + "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, - "dependencies": { - "@jest/console": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@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.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "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": ">=18" } }, - "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/@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": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "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==", + "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": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "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==", + "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" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=18" } }, - "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/@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/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/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-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": ">=8" + "node": ">=18" } }, - "node_modules/@jest/core/node_modules/jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "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, - "dependencies": { - "@jest/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "leven": "^3.1.0", - "pretty-format": "^27.4.6" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=18" } }, - "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==", + "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": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=8.6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "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": ">=8" + "node": "^12.0.0 || ^14.0.0 || >=16.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/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "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": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/environment": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", + "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": { - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6" + "type-fest": "^0.20.2" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/fake-timers": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", + "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": { - "@jest/types": "^27.4.2", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "argparse": "^2.0.1" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@jest/globals": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", + "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, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", + "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": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "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": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=16.0.0" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "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==", - "dev": true, + "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": { - "color-convert": "^2.0.1" + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "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, + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@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": ">=10" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "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-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": { - "color-name": "~1.1.4" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "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==", - "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/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": ">=8" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "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/@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/@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/@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/@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": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/source-map": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", + "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, - "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": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "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/@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": ">=0.10.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", - "dev": true, - "dependencies": { - "@jest/console": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "node": ">=12" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/test-sequencer": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } + "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/@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", + "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": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "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==", + "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": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?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==", + "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": { - "color-name": "~1.1.4" + "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": ">=7.0.0" + "node": ">=8" } }, - "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==", + "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/transform/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "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": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "@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": ">=8.6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/transform/node_modules/slash": { + "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==", @@ -2605,1775 +2952,1892 @@ "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==", + "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": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "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": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "@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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" }, "engines": { - "node": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "@jest/get-type": "30.1.0" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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.13.0", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.13.0.tgz", - "integrity": "sha512-akDiMq+CrF3m4ENA3DlEj+XfskZow6SqvrkOUVIStow5kUqcCBow635W7+YAem2TJNxH+CpVgpGV24osiQx+ZQ==", + "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": { - "@babel/generator": "^7.11.6", - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.13.0", - "mkdirp": "^1.0.4" + "@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": ">=10.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@lingui/cli": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.13.0.tgz", - "integrity": "sha512-hK/7z+hqxT9CSzUQUQEefurbjmZCJldLG9kbSp8mNgJ+XLAv1mWPve79pYCbtMK7M7vbyU4uG0ncnH+pHKFF/w==", - "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.13.0", - "@lingui/conf": "^3.13.0", - "babel-plugin-macros": "^3.0.1", - "bcp-47": "^1.0.7", - "chalk": "^4.1.0", - "chokidar": "3.5.1", - "cli-table": "0.3.6", - "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" - }, + "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": ">=10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "babel-plugin-macros": "2 || 3", - "typescript": "2 || 3 || 4" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@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": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@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/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "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" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">= 8.10.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, "optionalDependencies": { - "fsevents": "~2.3.1" + "@pkgjs/parseargs": "^0.11.0" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=7.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": ">= 6" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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/@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==", + "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": { - "binary-extensions": "^2.0.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@lingui/cli/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==", - "dev": true - }, - "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==", + "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": { - "picomatch": "^2.2.1" + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { - "node": ">=8.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.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==", + "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": { - "has-flag": "^4.0.0" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@lingui/conf": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.13.0.tgz", - "integrity": "sha512-1vl7NEZWMuiM2JCqnvlGmoyqlwB4isSEZrzvKWGAGMRLxMuuKR6PrH1Khgl4x2WRLZxfEysXTe6YR08Ra68irQ==", + "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": { - "@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" + "@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": ">=10.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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": { - "color-convert": "^2.0.1" + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "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/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": { - "color-name": "~1.1.4" + "@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": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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/@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/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/@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/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": { - "has-flag": "^4.0.0" + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@lingui/core": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.13.0.tgz", - "integrity": "sha512-UDmI8UL59rLmQDDjBK8JFMX0+i3+pncl3fWG+tD2cXNJkN+MEBrhECTQ2lsM1tCk09AfiATglPPXm1e0tLxxOw==", + "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": { - "@babel/runtime": "^7.11.2", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": ">=10.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@lingui/core/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/@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/@lingui/loader": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.13.0.tgz", - "integrity": "sha512-MuLwM6vVMYH/ilCGunNGIPTvZU9gtjaohU54/bDCRbDJ5teN46UKqFTYqP6TwVWa+AF6hRMcgUprBcQJi7Iw8Q==", + "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": { - "@babel/runtime": "^7.11.2", - "@lingui/cli": "^3.13.0", - "@lingui/conf": "^3.13.0", - "loader-utils": "^2.0.0", - "ramda": "^0.27.1" - }, + "@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": ">=10.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "node": ">=6.0.0" } }, - "node_modules/@lingui/macro": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.13.0.tgz", - "integrity": "sha512-TmwAiFnxtutDEKp7KFtUmq5vIfv56zn0FV0ZgrISUcW1liVlRyqW6YnQ7cv4AzsPnkBhO2+O2YVoHY1r5owMvA==", + "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": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.13.0", - "ramda": "^0.27.1" - }, + "@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": ">=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" + "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": ">=6.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": "^16.3.0", - "react-dom": "^16.3.0" + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "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==", + "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": ">=0.10.0" + "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/@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==", + "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": { - "@babel/runtime": "^7.2.0", - "deepmerge": "^3.0.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" }, "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "react": "^16.3.0", - "react-dom": "^16.3.0" + "node": ">=20.0.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==", + "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": ">=0.10.0" + "node": ">=20.0.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==", + "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.2.0", - "prop-types": "^15.6.0", - "react-is": "^16.6.3" + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.4.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": "^16.3.0", - "react-dom": "^16.3.0" + "@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/@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.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/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@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": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" }, "engines": { - "node": ">= 8" + "node": ">=20.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@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.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "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.5", - "fastq": "^1.6.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">= 8" + "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=" - }, - "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/@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": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "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": { - "@sinonjs/commons": "^1.7.0" + "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/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "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": ">= 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": ">=14" } }, - "node_modules/@types/babel__core": { - "version": "7.1.17", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.17.tgz", - "integrity": "sha512-6zzkezS9QEIL8yCBvXWxPTJPNuMeECJVxSOhxNY/jfq9LxOTHivaYTqr37n9LknWWRTIkzqH2UilS5QFvfa90A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } + "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/@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==", + "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": { - "@babel/types": "^7.0.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" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "@babel/types": "^7.3.0" - } - }, - "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==", - "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": "*" + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" } }, - "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==", + "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, - "peer": true, "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "punycode": "^2.1.0" } }, - "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/@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/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "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, - "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.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" } }, - "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/@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": { - "@types/node": "*" + "@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/@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/@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/@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/@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": { - "@types/istanbul-lib-coverage": "*" + "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/@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==", + "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": { - "@types/istanbul-lib-report": "*" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "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==", + "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": { - "csstype": "^2.0.0", - "indefinite-observable": "^1.0.1" + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "4.13.0", + "unraw": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" } }, - "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.4.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", - "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", - "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==", + "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": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "@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/@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==", + "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": { - "@types/react": "*" + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + }, + "engines": { + "node": ">=20.0.0" } }, - "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==", + "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": { - "@types/mime": "^1", - "@types/node": "*" + "@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/@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==", + "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": { - "@types/yargs-parser": "*" + "@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/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "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/@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==", + "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": { - "@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" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "eslint": "*" + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "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==", + "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": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "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" }, - "peerDependencies": { - "eslint": ">=5" + "engines": { + "node": ">=16.0.0" } }, - "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==", + "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": { - "@typescript-eslint/types": "4.31.1", - "@typescript-eslint/visitor-keys": "4.31.1" + "@babel/runtime": "^7.20.13", + "@lingui/core": "4.13.0" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": ">=16.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "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==", + "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": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^14.21.3 || >=16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://paulmillr.com/funding/" } }, - "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==", + "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": { - "@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" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 8" } }, - "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==", + "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": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": ">= 8" } }, - "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==", + "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": { - "@typescript-eslint/types": "4.31.1", - "eslint-visitor-keys": "^2.0.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 8" } }, - "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==", + "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, - "peer": true, + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@noble/hashes": "^1.1.5" } }, - "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==", + "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, - "peer": true + "optional": true, + "engines": { + "node": ">=14" + } }, - "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==", + "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, - "peer": true + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } }, - "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/@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/@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, + "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": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, - "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==", + "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.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==" + }, + "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, - "peer": true + "license": "MIT" }, - "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==", + "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, - "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" + "type-detect": "4.0.8" } }, - "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==", + "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, - "peer": true, "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "@sinonjs/commons": "^3.0.1" } }, - "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==", + "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, - "peer": true, + "optional": true, "dependencies": { - "@xtuc/long": "4.2.2" + "tslib": "^2.4.0" } }, - "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==", + "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, - "peer": true + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } }, - "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==", + "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, - "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" + "@babel/types": "^7.0.0" } }, - "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==", + "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, - "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" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "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==", + "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, - "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" + "@babel/types": "^7.3.0" } }, - "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==", + "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, - "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" + "@types/istanbul-lib-coverage": "*" } }, - "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==", + "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, - "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" + "@types/istanbul-lib-report": "*" } }, - "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/@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/@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/@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/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "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/accept-language": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", - "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=", + "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": { - "bcp47": "^1.1.2", - "stable": "^0.1.6" + "@types/yargs-parser": "*" } }, - "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/@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": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" }, "engines": { - "node": ">= 0.6" + "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/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "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, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" }, "engines": { - "node": ">=0.4.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "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==", + "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, - "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" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "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, + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { - "ajv": "^6.9.1" + "typescript": ">=4.8.4 <6.0.0" } }, - "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==", + "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": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "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==", + "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": { - "type-fest": "^0.21.3" + "@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": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "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==", + "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": ">=8" + "node": "18 || 20 || >=22" } }, - "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/@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": { - "color-convert": "^1.9.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=4" + "node": "18 || 20 || >=22" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "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": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/apollo-datasource": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.1.tgz", - "integrity": "sha512-Z3a8rEUXVPIZ1p8xrFL8bcNhWmhOmovgDArvwIwmJOBnh093ZpRfO+ESJEDAN4KswmyzCLDAwjsW4zQOONdRUw==", - "dependencies": { - "apollo-server-caching": "^3.3.0", - "apollo-server-env": "^4.2.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=12.0" - } - }, - "node_modules/apollo-reporting-protobuf": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.0.tgz", - "integrity": "sha512-51Jwrg0NvHJfKz7TIGU8+Os3rUAqWtXeKRsRtKYtTeMSBPNhzz8UoGjAB3XyVmUXRE3IRmLtDPDRFL7qbxMI/w==", - "dependencies": { - "@apollo/protobufjs": "1.2.2" - } - }, - "node_modules/apollo-server": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.6.3.tgz", - "integrity": "sha512-kNvOiDNkIaO+MsfR9v40Vz4ArlDdc9VwVKGJy5dniLW9AoDa/tSF99m8ItfGoMypqlRPMgrNGxkMuToBnvYXNQ==", - "dependencies": { - "apollo-server-core": "^3.6.3", - "apollo-server-express": "^3.6.3", - "express": "^4.17.1" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, - "node_modules/apollo-server-caching": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.3.0.tgz", - "integrity": "sha512-Wgcb0ArjZ5DjQ7ID+tvxUcZ7Yxdbk5l1MxZL8D8gkyjooOkhPNzjRVQ7ubPoXqO54PrOMOTm1ejVhsF+AfIirQ==", - "dependencies": { - "lru-cache": "^6.0.0" + "node": "18 || 20 || >=22" }, - "engines": { - "node": ">=12.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/apollo-server-core": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.6.3.tgz", - "integrity": "sha512-TFJmAlI6vPp1MHOSXqYkE6leAyMekWv/D/3ma11uETkcd3EPjERGmxtTXPJElMVEkOK9BEElYKthCrH7bjYLuw==", - "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", - "@josephg/resolvable": "^1.0.0", - "apollo-datasource": "^3.3.1", - "apollo-reporting-protobuf": "^3.3.0", - "apollo-server-caching": "^3.3.0", - "apollo-server-env": "^4.2.1", - "apollo-server-errors": "^3.3.1", - "apollo-server-plugin-base": "^3.5.1", - "apollo-server-types": "^3.5.1", - "async-retry": "^1.2.1", - "fast-json-stable-stringify": "^2.1.0", - "graphql-tag": "^2.11.0", - "lodash.sortby": "^4.7.0", - "loglevel": "^1.6.8", - "lru-cache": "^6.0.0", - "sha.js": "^2.4.11", - "uuid": "^8.0.0" + "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": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" + "node": ">=10" } }, - "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==", + "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": { - "node-fetch": "^2.6.7" + "@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": ">=12.0" - } - }, - "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==", - "engines": { - "node": ">=12.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, - "node_modules/apollo-server-express": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.3.tgz", - "integrity": "sha512-3CjahZ+n+1T7pHH1qW1B6Ns0BzwOMeupAp2u0+M8ruOmE/e7VKn0OSOQQckZ8Z2AcWxWeno9K89fIv3PoSYgYA==", - "dependencies": { - "@types/accepts": "^1.3.5", - "@types/body-parser": "1.19.2", - "@types/cors": "2.8.12", - "@types/express": "4.17.13", - "@types/express-serve-static-core": "4.17.28", - "accepts": "^1.3.5", - "apollo-server-core": "^3.6.3", - "apollo-server-types": "^3.5.1", - "body-parser": "^1.19.0", - "cors": "^2.8.5", - "parseurl": "^1.3.3" - }, - "engines": { - "node": ">=12.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "express": "^4.17.1", - "graphql": "^15.3.0 || ^16.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/apollo-server-plugin-base": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.5.1.tgz", - "integrity": "sha512-wgDHz3lLrCqpecDky3z6AOQ0vik0qs0Cya/Ti6n3ESYXJ9MdK3jE/QunATIrOYYJaa+NKl9V7YwU+/bojNfFuQ==", + "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": { - "apollo-server-types": "^3.5.1" + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": ">=12.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/apollo-server-types": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.1.tgz", - "integrity": "sha512-zG7xLl4mmHuZMAYOfjWKHY/IC/GgIkJ3HnYuR7FRrnPpRA9Yt5Kf1M1rjm1Esuqzpb/dt8pM7cX40QaIQObCYQ==", - "dependencies": { - "apollo-reporting-protobuf": "^3.3.0", - "apollo-server-caching": "^3.3.0", - "apollo-server-env": "^4.2.1" - }, + "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": ">=12.0" - }, - "peerDependencies": { - "graphql": "^15.3.0 || ^16.0.0" - } - }, - "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/arangojs": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.6.1.tgz", - "integrity": "sha512-GfZQykBwBdRl32/RGy1NLIq8LTYW+PW6uU10B5uuZe5gIJpo8YLQhJBu6XwGfEyQrpb4CEMhmK1SgqdRgFnSxA==", - "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" + "node": "^20.19.0 || ^22.13.0 || >=24" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "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/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "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, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "optional": true, + "os": [ + "android" + ] }, - "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/@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/array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "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, - "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" - } + "optional": true, + "os": [ + "darwin" + ] }, - "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==", + "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, - "engines": { - "node": ">=8" - } + "optional": true, + "os": [ + "darwin" + ] }, - "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==", + "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, - "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" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "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/@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/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "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/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/@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/assign-symbols": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", - "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==", + "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": ">=6" + "node": ">=14.0.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==", + "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": ">=8" + "node": ">=16.0.0" } }, - "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==", + "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": { - "retry": "0.13.1" + "bcp47": "^1.1.2", + "stable": "^0.1.6" } }, - "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/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/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, + "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": ">= 4.0.0" + "node": ">= 0.6" } }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "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": { - "follow-redirects": "^1.14.0" + "notation": "^1.3.6" } }, - "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==", + "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": { - "@babel/core": "^7.0.0-0" + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", + "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": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", - "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" + }, + "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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "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/babel-jest/node_modules/ansi-styles": { + "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==", @@ -4388,108 +4852,291 @@ "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 8" } }, - "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, + "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": { - "color-name": "~1.1.4" + "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": ">=7.0.0" + "node": ">=10" } }, - "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, + "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/babel-jest/node_modules/slash": { + "node_modules/arango-tools/node_modules/mime-kind": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "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" + "node": ">=8.3.0" } }, - "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, + "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": { - "has-flag": "^4.0.0" + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=8.3.0" } }, - "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, + "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.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "dependencies": { + "follow-redirects": "^1.16.0", + "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": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", + "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": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-plugin-macros": { @@ -4572,42 +5219,45 @@ "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==", + "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.8.3", - "@babel/plugin-syntax-import-meta": "^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.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.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-top-level-await": "^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" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", + "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": "^27.4.0", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/babel-runtime": { @@ -4634,11 +5284,6 @@ "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", @@ -4665,21 +5310,6 @@ } ] }, - "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", @@ -4693,13 +5323,16 @@ "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==", + "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": "*" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bl": { @@ -4714,29 +5347,33 @@ } }, "node_modules/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "dependencies": { - "bytes": "3.1.1", - "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.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" + "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.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "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" } @@ -4744,12 +5381,13 @@ "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=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "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==", + "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", @@ -4757,49 +5395,47 @@ } }, "node_modules/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, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, - "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.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "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.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "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" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/bser": { @@ -4838,7 +5474,7 @@ "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=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -4846,22 +5482,87 @@ "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.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", + "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/-/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==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "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" @@ -4886,33 +5587,61 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001286", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz", - "integrity": "sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ==", + "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" - } + "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": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "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/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/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", @@ -4923,18 +5652,11 @@ "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, - "optional": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4951,35 +5673,11 @@ "fsevents": "~2.3.2" } }, - "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, - "optional": true, - "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, - "optional": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "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, - "optional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -4987,33 +5685,27 @@ "node": ">=8.10.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==", + "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, - "peer": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "engines": { - "node": ">=6.0" + "node": ">=8" } }, - "node_modules/ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "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==", + "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/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5039,9 +5731,9 @@ } }, "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==", + "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" @@ -5050,34 +5742,18 @@ "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==", + "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.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/clone": { @@ -5114,7 +5790,7 @@ "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "engines": { "iojs": ">= 1.0.0", @@ -5122,30 +5798,33 @@ } }, "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==", + "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": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "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.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "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": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", "dev": true, "engines": { "node": ">=0.1.90" @@ -5155,7 +5834,6 @@ "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" }, @@ -5178,28 +5856,82 @@ "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==", + "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": { - "entities": "~2.0", - "mdurl": "~1.0.1", - "minimist": ">=1.2.2", - "string.prototype.repeat": "^0.2.0" + "mime-db": ">= 1.43.0 < 2" }, - "bin": { - "commonmark": "bin/commonmark" + "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": "*" + "node": ">= 0.8.0" } }, - "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/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", @@ -5211,6 +5943,7 @@ "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" }, @@ -5235,12 +5968,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5255,19 +5990,19 @@ } }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "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.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { @@ -5280,9 +6015,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "node_modules/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "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": { @@ -5297,28 +6032,18 @@ } }, "node_modules/core-js-compat": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.3.tgz", - "integrity": "sha512-c8M5h0IkNZ+I92QhIpuSijOxGAcj3lgpsWdkCqmUTZNwidujF4r3pi6x1DCN+Vcs5qTS2XWWMfWSuCqyupX8gw==", + "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.19.1", - "semver": "7.0.0" + "browserslist": "^4.22.2" }, "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/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -5347,35 +6072,33 @@ "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==", + "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.1" + "node-fetch": "2.6.7" } }, - "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, + "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": "4.x || >=6.0.0" + "node": ">=16.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==", + "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", @@ -5385,91 +6108,28 @@ "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==", + "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" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, - "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.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "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.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5480,17 +6140,19 @@ } } }, - "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/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true + "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", @@ -5499,9 +6161,9 @@ "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==", + "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" @@ -5516,6 +6178,22 @@ "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", @@ -5532,16 +6210,10 @@ "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", @@ -5551,19 +6223,25 @@ } }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "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.6" + "node": ">= 0.8" } }, "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": "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==", @@ -5573,45 +6251,15 @@ } }, "node_modules/dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", - "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", @@ -5624,40 +6272,11 @@ "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/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", @@ -5674,6 +6293,25 @@ "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", @@ -5685,21 +6323,22 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.17", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.17.tgz", - "integrity": "sha512-zhk1MravPtq/KBhmGB7TLBILmXTgRG9TFSI3qS3DbgyfHzIl72iiTE37r/BHIbPCJJlWIo5rySyxiH4vWhu2ZA==", + "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.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sindresorhus/emittery?sponsor=1" @@ -5711,73 +6350,15 @@ "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=", + "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/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/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", @@ -5821,12 +6402,46 @@ "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-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", @@ -5850,178 +6465,154 @@ "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-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==", + "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, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, + "hasInstallScript": true, + "license": "MIT", "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=6.0" + "node": ">=18" }, "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/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" + "@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/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=", + "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": ">= 0.8.0" + "node": ">=6" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } + "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/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=", + "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, - "dependencies": { - "prelude-ls": "~1.1.2" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, "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", + "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.0.1", + "debug": "^4.3.2", "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", + "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", - "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", + "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", - "js-yaml": "^3.13.1", + "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.0.4", + "minimatch": "^3.1.2", "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" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "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", @@ -6035,9 +6626,9 @@ } }, "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==", + "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": [ { @@ -6053,11 +6644,14 @@ "url": "https://feross.org/support" } ], + "engines": { + "node": ">=12.0.0" + }, "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" + "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": { @@ -6168,23 +6762,25 @@ "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==", + "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-utils": "^2.0.0", - "regexpp": "^3.0.0" + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" }, "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "eslint": ">=4.19.1" + "eslint": ">=8" } }, "node_modules/eslint-plugin-import": { @@ -6242,187 +6838,194 @@ "dev": true }, "node_modules/eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", + "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/experimental-utils": "^4.0.1" + "@typescript-eslint/utils": "^8.0.0" }, "engines": { - "node": ">=10" + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" + "@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-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==", + "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-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" + "@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": ">=8.10.0" + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": ">=5.16.0" + "eslint": ">=7.0.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==", + "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": ">= 4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-promise": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", - "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "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, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "bin": { + "semver": "bin/semver.js" }, - "peerDependencies": { - "eslint": "^7.0.0" + "engines": { + "node": ">=10" } }, - "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==", + "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, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, "engines": { - "node": ">=8.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "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==", + "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": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "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==", + "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": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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/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/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/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, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "is-glob": "^4.0.3" }, "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": ">=10.13.0" } }, "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==", + "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" @@ -6434,40 +7037,61 @@ "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==", + "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, - "engines": { - "node": ">=8" + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "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==", + "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": { - "lru-cache": "^6.0.0" + "p-locate": "^5.0.0" }, - "bin": { - "semver": "bin/semver.js" + "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/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/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": { - "has-flag": "^4.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/type-fest": { @@ -6483,26 +7107,20 @@ } }, "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==", + "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": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -6519,9 +7137,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "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" @@ -6530,15 +7148,6 @@ "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", @@ -6551,19 +7160,10 @@ "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==", + "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" @@ -6581,26 +7181,12 @@ "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==", + "license": "MIT", "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", @@ -6624,68 +7210,75 @@ "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=", + "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": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" + "@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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.1", - "content-disposition": "0.5.4", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.1", - "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", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.9.6", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "~1.5.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": { @@ -6701,14 +7294,34 @@ "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", @@ -6727,21 +7340,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "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" - } + ], + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -6749,44 +7349,16 @@ "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/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-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", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "node_modules/fast-safe-stringify": { @@ -6805,75 +7377,14 @@ } }, "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "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/fbjs": { - "version": "0.8.18", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz", - "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==", - "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.30" - } - }, - "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", @@ -6886,18 +7397,10 @@ "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": "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" @@ -6907,1191 +7410,98 @@ } }, "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-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/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": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", - "dev": true, - "dependencies": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/formidable/node_modules/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "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/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/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/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.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", - "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", - "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.30", - "resolved": "https://registry.npmjs.org/graphql-playground-html/-/graphql-playground-html-1.6.30.tgz", - "integrity": "sha512-tpCujhsJMva4aqE8ULnF7/l3xw4sNRZcSHu+R00VV+W0mfp+Q20Plvcrp+5UXD+2yS6oyCXncA+zoQJQqhGCEw==", - "dependencies": { - "xss": "^1.0.6" - } - }, - "node_modules/graphql-playground-middleware-express": { - "version": "1.7.23", - "resolved": "https://registry.npmjs.org/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.7.23.tgz", - "integrity": "sha512-M/zbTyC1rkgiQjFSgmzAv6umMHOphYLNWZp6Ye5QrD77WfGOOoSqDsVmGUczc2pDkEPEzzGB/bvBO5rdzaTRgw==", - "dependencies": { - "graphql-playground-html": "^1.6.30" - }, - "peerDependencies": { - "express": "^4.16.2" - } - }, - "node_modules/graphql-redis-subscriptions": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.4.2.tgz", - "integrity": "sha512-zMd1G6uZcEvqHZ6PsQ63BfRtw5Bg4cccql1pfkEQsj1vXVWe4p+o8DqNfd9DDUFXIqhHpqkSeN/49BlIwud4Ag==", - "dependencies": { - "iterall": "^1.3.0" - }, - "optionalDependencies": { - "ioredis": "^4.17.3" - }, - "peerDependencies": { - "graphql-subscriptions": "^1.0.0 || ^2.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.14.1", - "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.14.1.tgz", - "integrity": "sha512-IrJ2SI9IkCmWHyr7yIvtPNGWTWF3eTS+iNnw1DQMmEtsOgs1dUmT0ge+8M1+1xm+q3/5ZqB95yUYyThDyOTE+Q==", - "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 || ^16.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.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": ">=10" - }, - "peerDependencies": { - "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-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/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "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/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-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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/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.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "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/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/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.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==", - "engines": { - "node": ">= 0.10" - } - }, - "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-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.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-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "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-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-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-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-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, + "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": ">= 0.4" + "node": ">= 18.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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==", + "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": ">=0.12.0" + "node": ">=6" } }, - "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==", + "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": { - "has-tostringtag": "^1.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "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==", + "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": { - "isobject": "^3.0.1" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=0.10.0" + "node": "^10.12.0 || >=12.0.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==", + "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/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, + "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": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -8100,1130 +7510,1147 @@ "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==", + "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/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": ">=8" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "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, + "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": { - "has-tostringtag": "^1.0.0" + "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": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 6" } }, - "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==", + "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": { - "has-symbols": "^1.0.2" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" }, "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "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/is-typedarray": { + "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/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "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==", + "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": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "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" - }, + "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/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/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/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "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": ">=0.10.0" + "node": "6.* || 8.* || >= 10.*" } }, - "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==", + "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": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.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/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "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" + "node": ">=8.0.0" } }, - "node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, + "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": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "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==", + "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, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "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": ">=8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "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": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "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==", + "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": ">=0.10.0" + "node": ">=4" } }, - "node_modules/istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^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" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/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/jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", - "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": { - "@jest/core": "^27.4.7", - "import-local": "^3.0.2", - "jest-cli": "^27.4.7" - }, - "bin": { - "jest": "bin/jest.js" + "arrify": "^1.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=6.0.0" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "graphql": "*" } }, - "node_modules/jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "execa": "^5.0.0", - "throat": "^6.0.1" + "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" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "peerDependencies": { + "graphql-subscriptions": "^1.0.0 || ^2.0.0" } }, - "node_modules/jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, + "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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=0.10" } }, - "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, + "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": { - "color-convert": "^2.0.1" + "@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": ">=8" + "node": ">=12.22.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/ioredis" } }, - "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, + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "tslib": "^2.5.0" }, "engines": { "node": ">=10" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "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/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, + "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": { - "color-name": "~1.1.4" + "iterall": "^1.3.0" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "graphql": "^15.7.2 || ^16.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/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/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "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": ">=8" + "node": ">= 0.4.0" } }, - "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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", + "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, - "dependencies": { - "@jest/core": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "prompts": "^2.0.1", - "yargs": "^16.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": ">=4" } }, - "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, + "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": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "es-define-property": "^1.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { - "color-name": "~1.1.4" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" } }, - "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==", + "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": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-cli/node_modules/jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", - "dev": true, + "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": { - "@jest/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "leven": "^3.1.0", - "pretty-format": "^27.4.6" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10.17.0" } }, - "node_modules/jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", - "dev": true, + "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": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "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": ">=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/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==", + "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": { - "color-convert": "^2.0.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "find-up": "^4.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "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/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/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.8.19" } }, - "node_modules/jest-config/node_modules/jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "leven": "^3.1.0", - "pretty-format": "^27.4.6" + "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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">= 0.4" } }, - "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==", + "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": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "has-bigints": "^1.0.1" }, - "engines": { - "node": ">=8.6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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/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==", + "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": { - "has-flag": "^4.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "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": { - "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "builtin-modules": "^3.3.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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" - }, + "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": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": ">=8" + "node": ">=0.10.0" } }, - "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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/jest-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", + "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, - "dependencies": { - "detect-newline": "^3.0.0" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=6" } }, - "node_modules/jest-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", + "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": { - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" + "is-extglob": "^2.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=0.10.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==", + "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, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=0.12.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==", + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/jest-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", + "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": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", - "jsdom": "^16.6.0" + "isobject": "^3.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-environment-node": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", + "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": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "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, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", + "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, - "dependencies": { - "@jest/types": "^27.4.2", - "@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.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", + "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": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "throat": "^6.0.1" + "has-symbols": "^1.0.2" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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": { - "color-convert": "^2.0.1" + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "call-bind": "^1.0.2" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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/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==", + "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": ">=8" + "node": ">=0.10.0" } }, - "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, + "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": { - "has-flag": "^4.0.0" - }, + "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/jest-leak-detector": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", + "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": { - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "@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.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" } }, - "node_modules/jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "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, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "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" - }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "semver": "^7.5.3" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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, - "dependencies": { - "color-name": "~1.1.4" + "bin": { + "semver": "bin/semver.js" }, "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": ">=10" } }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { + "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==", @@ -9235,208 +8662,284 @@ "node": ">=8" } }, - "node_modules/jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", + "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": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=10" + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "@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": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/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/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-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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.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==", + "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": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=8.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", + "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, - "dependencies": { - "@jest/types": "^27.4.2", - "@types/node": "*" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=8" } }, - "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, + "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": ">=6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "jest-resolve": "*" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "jest-resolve": { + "node-notifier": { "optional": true } } }, - "node_modules/jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", + "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.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-resolve": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", + "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/types": "^27.4.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" + "@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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-resolve-dependencies": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" + "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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/jest-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==", + "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": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "balanced-match": "^1.0.0" } }, - "node_modules/jest-resolve/node_modules/camelcase": { + "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==", @@ -9448,768 +8951,804 @@ "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=10" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "@isaacs/cliui": "^8.0.2" }, - "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" + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest-resolve/node_modules/jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "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/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", + "@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": "^27.4.6" + "pretty-format": "30.3.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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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-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-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", - "dev": true, - "dependencies": { - "@jest/console": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "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==", + "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": { - "color-convert": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "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==", + "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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "slash": "^3.0.0", - "strip-bom": "^4.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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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": { - "color-convert": "^2.0.1" + "@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": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "@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": ">=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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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, - "engines": { - "node": ">=8" + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "@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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" } }, - "node_modules/jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", + "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, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@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.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", - "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", - "semver": "^7.3.2" + "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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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": { - "color-convert": "^2.0.1" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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" + "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": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "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==", + "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-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==", + "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": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" + "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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", + "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": { - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", - "picomatch": "^2.2.3" + "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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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": { - "color-convert": "^2.0.1" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "@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": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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==", + "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-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==", + "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": { - "has-flag": "^4.0.0" + "@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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "@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" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "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": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "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": ">= 10.14.2" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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": { - "@types/yargs-parser": "*" + "balanced-match": "^1.0.0" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=8" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", - "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", + "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, - "engines": { - "node": ">=10" + "dependencies": { + "@isaacs/cliui": "^8.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "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-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==", + "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.14.2" + "node": ">=10" } }, - "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==", + "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": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "@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": ">= 10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-watcher": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", + "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/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-util": "^27.4.2", - "string-length": "^4.0.1" + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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": { - "color-convert": "^2.0.1" + "@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": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "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-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==", + "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": { - "color-name": "~1.1.4" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "@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": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "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.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": ">= 10.13.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/has-flag": { @@ -10236,15 +9775,29 @@ "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.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "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", @@ -10254,83 +9807,18 @@ "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.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "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==", + "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": ">=4" + "node": ">=6" } }, - "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, - "peer": 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", @@ -10359,13 +9847,10 @@ "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==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -10373,22 +9858,10 @@ "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==", + "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", @@ -10399,124 +9872,43 @@ "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "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==", + "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": { - "warning": "^3.0.0" + "lru-cache": "^6.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" + "bin": { + "semver": "bin/semver.js" }, - "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" + "engines": { + "node": ">=10" } }, "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==", "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==", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -10529,15 +9921,6 @@ "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/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10566,30 +9949,6 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, - "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", @@ -10602,17 +9961,6 @@ "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", @@ -10638,7 +9986,7 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, "node_modules/lodash.isarguments": { "version": "3.1.0", @@ -10648,27 +9996,27 @@ "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=" + "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": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "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": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "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": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "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": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -10679,18 +10027,12 @@ "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=" + "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": "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 + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -10708,76 +10050,6 @@ "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.8.0", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", @@ -10831,20 +10103,14 @@ } }, "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==", + "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-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": "7.1.0", "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.1.0.tgz", @@ -10859,23 +10125,31 @@ "tmpl": "1.0.5" } }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + "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": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "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=" + "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", @@ -10883,20 +10157,6 @@ "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", @@ -10906,22 +10166,24 @@ } }, "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": { "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" }, @@ -10930,31 +10192,19 @@ } }, "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" - }, + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { - "node": ">=8.3.0" + "node": ">= 0.6" } }, "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==", + "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.49.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -10970,17 +10220,17 @@ } }, "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==", "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==", + "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" @@ -10990,46 +10240,38 @@ } }, "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/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" } }, "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==", + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "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/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" }, - "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/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", @@ -11039,11 +10281,31 @@ "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/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", @@ -11052,19 +10314,24 @@ "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": "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/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/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", @@ -11077,9 +10344,9 @@ } }, "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==", + "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" @@ -11123,27 +10390,60 @@ "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==", - "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=", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "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", @@ -11153,19 +10453,23 @@ "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/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": "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==", + "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": "^0.21.1", - "jsonwebtoken": "^8.2.1", - "underscore": "^1.9.0" + "axios": "^1.7.2", + "jsonwebtoken": "^9.0.2" + }, + "engines": { + "node": ">=14.17.3", + "npm": ">=6.14.13" } }, "node_modules/npm-run-path": { @@ -11180,12 +10484,6 @@ "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", @@ -11195,10 +10493,13 @@ } }, "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, + "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" } @@ -11265,9 +10566,10 @@ } }, "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==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -11275,6 +10577,14 @@ "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", @@ -11300,9 +10610,9 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "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", @@ -11310,7 +10620,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -11339,85 +10649,6 @@ "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-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -11462,11 +10693,12 @@ "node": ">=6" } }, - "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/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", @@ -11512,16 +10744,11 @@ "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==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11559,10 +10786,37 @@ "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.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", @@ -11574,15 +10828,15 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "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.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "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" @@ -11601,9 +10855,9 @@ } }, "node_modules/pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "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" @@ -11611,75 +10865,17 @@ }, "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/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "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": ">=8" + "node": ">=6" } }, - "node_modules/pkg-up/node_modules/find-up": { + "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==", @@ -11691,7 +10887,7 @@ "node": ">=6" } }, - "node_modules/pkg-up/node_modules/locate-path": { + "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==", @@ -11704,7 +10900,7 @@ "node": ">=6" } }, - "node_modules/pkg-up/node_modules/p-locate": { + "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==", @@ -11716,7 +10912,7 @@ "node": ">=6" } }, - "node_modules/pkg-up/node_modules/path-exists": { + "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=", @@ -11725,26 +10921,19 @@ "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==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz", + "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==", "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/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": { @@ -11769,19 +10958,37 @@ } }, "node_modules/pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "@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": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "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", @@ -11802,61 +11009,17 @@ "node": ">= 0.6.0" } }, - "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.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/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==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -11865,20 +11028,44 @@ "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": "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": { - "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/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", @@ -11889,10 +11076,29 @@ "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.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "dependencies": { + "side-channel": "^1.1.0" + }, "engines": { "node": ">=0.6" }, @@ -11920,112 +11126,49 @@ } ] }, - "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==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "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.1", - "http-errors": "1.8.1", - "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/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "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", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "loose-envify": "^1.1.0" }, "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==", + "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/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/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -12040,27 +11183,18 @@ "node": ">= 6" } }, - "node_modules/recompose": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", - "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", + "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": { - "@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" + "picomatch": "^2.2.1" }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.0 || ^16.0.0" + "engines": { + "node": ">=8.10.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", @@ -12106,7 +11240,8 @@ "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==" + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.14.5", @@ -12117,18 +11252,6 @@ "@babel/runtime": "^7.8.4" } }, - "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", @@ -12176,29 +11299,27 @@ "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==", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "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==", + "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.2.0", - "path-parse": "^1.0.6" + "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" @@ -12225,13 +11346,13 @@ "node": ">=8" } }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "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, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/restore-cursor": { @@ -12280,15 +11401,6 @@ "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", @@ -12312,24 +11424,6 @@ "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", @@ -12340,79 +11434,34 @@ "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==", + "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.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "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": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", + "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": "1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -12422,6 +11471,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -12429,59 +11479,86 @@ "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.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "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" - } + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.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/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==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "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==", + "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.1", - "safe-buffer": "^5.0.1" + "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", @@ -12500,30 +11577,88 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "dependencies": { - "shebang-regex": "^3.0.0" + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "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" @@ -12535,71 +11670,41 @@ "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": { + "node_modules/simple-update-notifier": { "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==", + "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": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "semver": "^7.5.3" }, "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==", + "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, - "dependencies": { - "color-convert": "^2.0.1" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10" } }, - "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==", + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=6" } }, - "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/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -12631,7 +11736,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "node_modules/stable": { @@ -12640,9 +11745,9 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "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" @@ -12651,26 +11756,18 @@ "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/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string_decoder": { @@ -12716,23 +11813,33 @@ } }, "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==", + "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.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-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", @@ -12761,378 +11868,159 @@ } }, "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, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.0" + "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/subscriptions-transport-ws": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", - "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", - "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": "^15.7.2 || ^16.0.0" - } - }, - "node_modules/superagent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.0.2.tgz", - "integrity": "sha512-2Kx35bZxLLJMBKtuXezxvD0aZQ7l923VwoCn7EtUx+aFxdG7co7PeRIddfrNtvvMuGaLZXA0mKzX+yWRhjrJ7A==", - "dev": true, - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.3", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "^2.5.0", - "qs": "^6.10.1", - "readable-stream": "^3.6.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">=6.4.0 <13 || >=14" - } - }, - "node_modules/superagent/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "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/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "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.2.1", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.1.tgz", - "integrity": "sha512-2kBKhfZgnPLmjpzB0n7A2ZnEAWTaLXq4bn3EEVY9w8rUpLyIlSusqKKvWA1Cav7hxXBnXGpxBsSeOHj5wQGe1Q==", - "dev": true, - "dependencies": { - "methods": "^1.1.2", - "superagent": "^7.0.2" - }, - "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==", + "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": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { + "node_modules/strip-bom": { "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==", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "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==", + "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, - "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": ">=6" } }, - "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==", + "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, - "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": ">=8" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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, - "peer": 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": ">=6" + "node": ">=14.18.0" } }, - "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==", + "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, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0.0" } }, - "node_modules/terser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz", - "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==", + "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, - "peer": true, "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" + "lru-cache": "^6.0.0" }, "bin": { - "terser": "bin/terser" + "semver": "bin/semver.js" }, "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==", + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", "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" + "methods": "^1.1.2", + "superagent": "^9.0.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": ">=14.18.0" } }, - "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==", + "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, - "peer": true, "dependencies": { - "yocto-queue": "^0.1.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "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==", + "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, - "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, - "peer": true, + "dependencies": { + "@pkgr/core": "^0.2.9" + }, "engines": { - "node": ">= 8" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" } }, "node_modules/test-exclude": { @@ -13155,33 +12043,49 @@ "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==", + "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": { @@ -13190,15 +12094,40 @@ "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, + "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": ">=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==", + "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", @@ -13215,69 +12144,31 @@ "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/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==", + "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, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" } }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "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, - "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" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=2.7" + "typescript": ">=4.8.4" } }, "node_modules/tsconfig-paths": { @@ -13293,9 +12184,9 @@ } }, "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" @@ -13314,30 +12205,15 @@ } }, "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" - } + "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/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/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", @@ -13376,6 +12252,7 @@ "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" @@ -13384,45 +12261,33 @@ "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, + "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": { - "is-typedarray": "^1.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" } }, "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==", + "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": ">=4.2.0" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { @@ -13440,10 +12305,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/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", @@ -13485,23 +12357,84 @@ "node": ">=4" } }, - "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=", + "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", @@ -13525,47 +12458,44 @@ "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "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==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", "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 + "uuid": "dist/esm/bin/uuid" + } }, "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "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": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^2.0.0" }, "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/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", @@ -13580,21 +12510,14 @@ } }, "node_modules/validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "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/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==", - "engines": { - "node": ">=12" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -13603,33 +12526,6 @@ "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.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -13647,20 +12543,6 @@ "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", @@ -13670,130 +12552,11 @@ "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", @@ -13825,10 +12588,31 @@ "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.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, "engines": { "node": ">=0.10.0" @@ -13851,39 +12635,24 @@ "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==", + "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": { - "color-convert": "^2.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "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" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "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", @@ -13891,35 +12660,28 @@ "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==", + "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", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "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==", + "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": ">=8.3.0" + "node": ">=14" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/x3-linkedlist": { @@ -13941,38 +12703,6 @@ "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", @@ -14005,39 +12735,30 @@ } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "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": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "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==", + "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": ">=6" + "node": ">=12" } }, "node_modules/yocto-queue": { @@ -14045,7 +12766,6 @@ "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" }, @@ -14055,46 +12775,251 @@ } }, "dependencies": { - "@apollo/protobufjs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz", - "integrity": "sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==", + "@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" + } + }, + "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": { - "@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" + "@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": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + "@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" + } } } }, - "@apollographql/apollo-tools": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.2.tgz", - "integrity": "sha512-KxZiw0Us3k1d0YkJDhOpVH5rJ+mBfjXcgoRoCcslbgirjgLotKMzOcx4PZ7YTEvvEROmvG7X3Aon41GvMmyGsw==" + "@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==" }, - "@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==", + "@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": { - "xss": "^1.0.8" + "@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", @@ -14113,52 +13038,64 @@ } }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "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/highlight": "^7.16.7" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, "@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "convert-source-map": "^1.7.0", + "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.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "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.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "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/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@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": { @@ -14181,15 +13118,33 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "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.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" + "@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": { @@ -14234,13 +13189,10 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } + "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", @@ -14252,33 +13204,20 @@ } }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "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/types": "^7.16.7" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } + "@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-member-expression-to-functions": { "version": "7.16.7", @@ -14290,28 +13229,24 @@ } }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "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/types": "^7.16.7" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "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-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" } }, "@babel/helper-optimise-call-expression": { @@ -14324,9 +13259,9 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "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": { @@ -14372,24 +13307,30 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "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.16.7" + "@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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "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": { @@ -14405,25 +13346,13 @@ } }, "@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "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/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/node": { @@ -14441,10 +13370,13 @@ } }, "@babel/parser": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", - "dev": true + "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", @@ -14678,6 +13610,15 @@ "@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", @@ -14697,12 +13638,12 @@ } }, "@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.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.14.5" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -14778,12 +13719,12 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-arrow-functions": { @@ -14949,16 +13890,15 @@ } }, "@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==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "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/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" } }, "@babel/plugin-transform-modules-umd": { @@ -15209,50 +14149,54 @@ } }, "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "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.13.4" + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "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.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/traverse": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", + "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.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" + "@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.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "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-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, "@bcoe/v8-coverage": { @@ -15261,149 +14205,412 @@ "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==", + "@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": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" + "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": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "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.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "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.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "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 - } - } - }, - "@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" + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, - "@graphql-tools/merge": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.3.tgz", - "integrity": "sha512-XCSmL6/Xg8259OTWNp69B57CPWiVL69kB7pposFrufG/zaAlI9BS68dgzrxmmSqZV5ZHU4r/6Tbf6fwnEJGiSw==", - "requires": { - "@graphql-tools/utils": "^8.6.2", - "tslib": "~2.3.0" - } + "@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/mock": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.5.2.tgz", - "integrity": "sha512-5BosbTWkzo5tdxIqoqokGLDPmdTS1tE4QNm6a2ONlXz0MaynPRAQ8b2CcSy/c6r0lDmCdkLtbVrRtV6m/wE6Kw==", + "@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/schema": "^8.3.2", - "@graphql-tools/utils": "^8.6.2", - "fast-json-stable-stringify": "^2.1.0", - "tslib": "~2.3.0" + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" } }, "@graphql-tools/schema": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.2.tgz", - "integrity": "sha512-77feSmIuHdoxMXRbRyxE8rEziKesd/AcqKV6fmxe7Zt+PgIQITxNDew2XJJg7qFTMNM43W77Ia6njUSBxNOkwg==", + "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": "^8.2.3", - "@graphql-tools/utils": "^8.6.2", - "tslib": "~2.3.0", - "value-or-promise": "1.0.11" + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" } }, "@graphql-tools/utils": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.2.tgz", - "integrity": "sha512-x1DG0cJgpJtImUlNE780B/dfp8pxvVxOD6UeykFH5rHes26S4kGokbgU8F1IgrJ1vAPm/OVBHtd2kicTsPfwdA==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.11.0.tgz", + "integrity": "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==", "requires": { - "tslib": "~2.3.0" + "@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.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "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": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@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": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "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", @@ -15424,180 +14631,80 @@ "dev": true }, "@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", + "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": "^27.4.2", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.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.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", + "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": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@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.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.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": { - "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.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "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.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "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/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", + "@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": "^27.4.6" - } - }, - "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" + "pretty-format": "30.3.0" } }, "slash": { @@ -15605,433 +14712,467 @@ "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/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": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", + "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": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "^27.4.6" + "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": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", + "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": "^27.4.2", - "@sinonjs/fake-timers": "^8.0.1", + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "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": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", + "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": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" + "@types/node": "*", + "jest-regex-util": "30.0.1" } }, "@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", + "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": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@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.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", + "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": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.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==", + "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": { - "color-convert": "^2.0.1" + "balanced-match": "^1.0.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" } }, - "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==", + "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 }, - "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 + "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 - }, - "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/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": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", + "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": { - "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 - } + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" } }, "@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", + "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": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@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": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", + "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": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" + "@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": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", + "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.1.0", - "@jest/types": "^27.4.2", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "@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", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "write-file-atomic": "^5.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==", + "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 }, - "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==", + } + } + }, + "@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": { - "has-flag": "^4.0.0" + "@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 } } }, - "@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "@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": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "@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": { - "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==", + "@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": { - "color-name": "~1.1.4" + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.4.1" } }, - "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==", + "@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": { - "has-flag": "^4.0.0" + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" } } } }, - "@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.13.0", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.13.0.tgz", - "integrity": "sha512-akDiMq+CrF3m4ENA3DlEj+XfskZow6SqvrkOUVIStow5kUqcCBow635W7+YAem2TJNxH+CpVgpGV24osiQx+ZQ==", - "dev": true, - "requires": { - "@babel/generator": "^7.11.6", - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.13.0", - "mkdirp": "^1.0.4" - } - }, "@lingui/cli": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.13.0.tgz", - "integrity": "sha512-hK/7z+hqxT9CSzUQUQEefurbjmZCJldLG9kbSp8mNgJ+XLAv1mWPve79pYCbtMK7M7vbyU4uG0ncnH+pHKFF/w==", - "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.13.0", - "@lingui/conf": "^3.13.0", - "babel-plugin-macros": "^3.0.1", - "bcp-47": "^1.0.7", - "chalk": "^4.1.0", + "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.6", - "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", "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.8.0-beta.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==", + "@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": { - "color-convert": "^2.0.1" + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" } }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "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 }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "balanced-match": "^4.0.2" } }, "chokidar": { @@ -16050,248 +15191,248 @@ "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==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "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==", + "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 }, - "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==", + "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": { - "binary-extensions": "^2.0.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" } }, - "make-plural": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.2.tgz", - "integrity": "sha512-8iTuFioatnTTmb/YJjywkVIHLjcwkFD9Ms0JpxjEm9Mo8eQYkh1z+55dwv4yc1jQ8ftVBxWQbihvZL1DfzGGWA==", - "dev": true + "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" + } }, - "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==", + "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": { - "picomatch": "^2.2.1" + "whatwg-url": "^7.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==", + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "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": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.13.0.tgz", - "integrity": "sha512-1vl7NEZWMuiM2JCqnvlGmoyqlwB4isSEZrzvKWGAGMRLxMuuKR6PrH1Khgl4x2WRLZxfEysXTe6YR08Ra68irQ==", + "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.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" + "@babel/runtime": "^7.20.13", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^1.17.1", + "picocolors": "^1.1.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==", + "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": { - "color-convert": "^2.0.1" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "argparse": "^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==", + } + } + }, + "@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": { - "color-name": "~1.1.4" + "@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" } }, - "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==", + "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 }, - "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 + "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" + } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "argparse": "^2.0.1" } } } }, - "@lingui/core": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.13.0.tgz", - "integrity": "sha512-UDmI8UL59rLmQDDjBK8JFMX0+i3+pncl3fWG+tD2cXNJkN+MEBrhECTQ2lsM1tCk09AfiATglPPXm1e0tLxxOw==", + "@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": { - "@babel/runtime": "^7.11.2", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3" - }, - "dependencies": { - "make-plural": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.2.tgz", - "integrity": "sha512-8iTuFioatnTTmb/YJjywkVIHLjcwkFD9Ms0JpxjEm9Mo8eQYkh1z+55dwv4yc1jQ8ftVBxWQbihvZL1DfzGGWA==" - } + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" } }, - "@lingui/loader": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.13.0.tgz", - "integrity": "sha512-MuLwM6vVMYH/ilCGunNGIPTvZU9gtjaohU54/bDCRbDJ5teN46UKqFTYqP6TwVWa+AF6hRMcgUprBcQJi7Iw8Q==", + "@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.11.2", - "@lingui/cli": "^3.13.0", - "@lingui/conf": "^3.13.0", - "loader-utils": "^2.0.0", - "ramda": "^0.27.1" - } - }, - "@lingui/macro": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.13.0.tgz", - "integrity": "sha512-TmwAiFnxtutDEKp7KFtUmq5vIfv56zn0FV0ZgrISUcW1liVlRyqW6YnQ7cv4AzsPnkBhO2+O2YVoHY1r5owMvA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.13.0", - "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==" - } + "@babel/runtime": "^7.20.13", + "@lingui/core": "4.13.0" } }, - "@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==", + "@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.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==" - } + "moo": "^0.5.1" } }, - "@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==", + "@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": { - "@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==" - } + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, "@nicolo-ribaudo/chokidar-2": { @@ -16301,6 +15442,12 @@ "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", @@ -16327,10 +15474,32 @@ "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": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" }, "@protobufjs/base64": { "version": "1.1.2", @@ -16345,12 +15514,12 @@ "@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==" }, "@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==", "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -16359,68 +15528,70 @@ "@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==" }, "@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==" }, "@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==" }, "@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==" }, "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==" + }, + "@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": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "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": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "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": "^1.7.0" + "@sinonjs/commons": "^3.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==", + "@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": { - "@types/node": "*" + "tslib": "^2.4.0" } }, "@types/babel__core": { - "version": "7.1.17", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.17.tgz", - "integrity": "sha512-6zzkezS9QEIL8yCBvXWxPTJPNuMeECJVxSOhxNY/jfq9LxOTHivaYTqr37n9LknWWRTIkzqH2UilS5QFvfa90A==", + "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.1.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" @@ -16454,91 +15625,10 @@ "@babel/types": "^7.3.0" } }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "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.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "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==", + "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": { @@ -16551,126 +15641,49 @@ } }, "@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==", + "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/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.4.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", - "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", - "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==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, - "@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==", + "@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": { - "@types/mime": "^1", - "@types/node": "*" + "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.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "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": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "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": "*" @@ -16682,263 +15695,268 @@ "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==", + "@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": { - "@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/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" } }, "@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==", + "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": "4.31.1", - "@typescript-eslint/visitor-keys": "4.31.1" + "@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": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz", - "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==", + "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": "4.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz", - "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==", + "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/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" + "@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": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "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": { - "lru-cache": "^6.0.0" + "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/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==", + "@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": { - "@typescript-eslint/types": "4.31.1", - "eslint-visitor-keys": "^2.0.0" + "@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" } }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "@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, - "peer": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@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 + } } }, - "@webassemblyjs/floating-point-hex-parser": { + "@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/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "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, - "peer": true + "optional": true }, - "@webassemblyjs/helper-api-error": { + "@unrs/resolver-binding-android-arm64": { "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==", + "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, - "peer": true + "optional": true }, - "@webassemblyjs/helper-buffer": { + "@unrs/resolver-binding-darwin-arm64": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "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, - "peer": true + "optional": true }, - "@webassemblyjs/helper-numbers": { + "@unrs/resolver-binding-darwin-x64": { "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==", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", "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" - } + "optional": true }, - "@webassemblyjs/helper-wasm-bytecode": { + "@unrs/resolver-binding-freebsd-x64": { "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==", + "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, - "peer": true + "optional": true }, - "@webassemblyjs/helper-wasm-section": { + "@unrs/resolver-binding-linux-arm-gnueabihf": { "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==", + "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, - "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" - } + "optional": true }, - "@webassemblyjs/ieee754": { + "@unrs/resolver-binding-linux-arm-musleabihf": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "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, - "peer": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } + "optional": true }, - "@webassemblyjs/leb128": { + "@unrs/resolver-binding-linux-arm64-gnu": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "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, - "peer": true, - "requires": { - "@xtuc/long": "4.2.2" - } + "optional": true }, - "@webassemblyjs/utf8": { + "@unrs/resolver-binding-linux-arm64-musl": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "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, - "peer": true + "optional": true }, - "@webassemblyjs/wasm-edit": { + "@unrs/resolver-binding-linux-ppc64-gnu": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "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, - "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" - } + "optional": true }, - "@webassemblyjs/wasm-gen": { + "@unrs/resolver-binding-linux-riscv64-gnu": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "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, - "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" - } + "optional": true }, - "@webassemblyjs/wasm-opt": { + "@unrs/resolver-binding-linux-riscv64-musl": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "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, - "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" - } + "optional": true }, - "@webassemblyjs/wasm-parser": { + "@unrs/resolver-binding-linux-s390x-gnu": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "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, - "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" - } + "optional": true }, - "@webassemblyjs/wast-printer": { + "@unrs/resolver-binding-linux-x64-gnu": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "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, - "peer": 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": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" + "@napi-rs/wasm-runtime": "^0.2.11" } }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "@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, - "peer": true + "optional": 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==", + "@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, - "peer": true + "optional": 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 + "@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", @@ -16950,30 +15968,35 @@ } }, "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" + }, + "dependencies": { + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + } } }, - "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, + "accesscontrol": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/accesscontrol/-/accesscontrol-2.2.1.tgz", + "integrity": "sha512-52EvFk/J9EF+w4mYQoKnOTkEMj01R1U5n2fc1dai6x1xkgOks3DGkx01qQL2cKFxGmE4Tn1krAU3jJA9L1NMkg==", "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "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", @@ -16981,25 +16004,10 @@ "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==", + "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", @@ -17008,20 +16016,6 @@ "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-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", @@ -17038,136 +16032,24 @@ "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==", + "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": "^1.9.0" + "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==", + "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" } }, - "apollo-datasource": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.1.tgz", - "integrity": "sha512-Z3a8rEUXVPIZ1p8xrFL8bcNhWmhOmovgDArvwIwmJOBnh093ZpRfO+ESJEDAN4KswmyzCLDAwjsW4zQOONdRUw==", - "requires": { - "apollo-server-caching": "^3.3.0", - "apollo-server-env": "^4.2.1" - } - }, - "apollo-reporting-protobuf": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.0.tgz", - "integrity": "sha512-51Jwrg0NvHJfKz7TIGU8+Os3rUAqWtXeKRsRtKYtTeMSBPNhzz8UoGjAB3XyVmUXRE3IRmLtDPDRFL7qbxMI/w==", - "requires": { - "@apollo/protobufjs": "1.2.2" - } - }, - "apollo-server": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.6.3.tgz", - "integrity": "sha512-kNvOiDNkIaO+MsfR9v40Vz4ArlDdc9VwVKGJy5dniLW9AoDa/tSF99m8ItfGoMypqlRPMgrNGxkMuToBnvYXNQ==", - "requires": { - "apollo-server-core": "^3.6.3", - "apollo-server-express": "^3.6.3", - "express": "^4.17.1" - } - }, - "apollo-server-caching": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.3.0.tgz", - "integrity": "sha512-Wgcb0ArjZ5DjQ7ID+tvxUcZ7Yxdbk5l1MxZL8D8gkyjooOkhPNzjRVQ7ubPoXqO54PrOMOTm1ejVhsF+AfIirQ==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "apollo-server-core": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.6.3.tgz", - "integrity": "sha512-TFJmAlI6vPp1MHOSXqYkE6leAyMekWv/D/3ma11uETkcd3EPjERGmxtTXPJElMVEkOK9BEElYKthCrH7bjYLuw==", - "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", - "@josephg/resolvable": "^1.0.0", - "apollo-datasource": "^3.3.1", - "apollo-reporting-protobuf": "^3.3.0", - "apollo-server-caching": "^3.3.0", - "apollo-server-env": "^4.2.1", - "apollo-server-errors": "^3.3.1", - "apollo-server-plugin-base": "^3.5.1", - "apollo-server-types": "^3.5.1", - "async-retry": "^1.2.1", - "fast-json-stable-stringify": "^2.1.0", - "graphql-tag": "^2.11.0", - "lodash.sortby": "^4.7.0", - "loglevel": "^1.6.8", - "lru-cache": "^6.0.0", - "sha.js": "^2.4.11", - "uuid": "^8.0.0" - } - }, - "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==", - "requires": { - "node-fetch": "^2.6.7" - } - }, - "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==", - "requires": {} - }, - "apollo-server-express": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.3.tgz", - "integrity": "sha512-3CjahZ+n+1T7pHH1qW1B6Ns0BzwOMeupAp2u0+M8ruOmE/e7VKn0OSOQQckZ8Z2AcWxWeno9K89fIv3PoSYgYA==", - "requires": { - "@types/accepts": "^1.3.5", - "@types/body-parser": "1.19.2", - "@types/cors": "2.8.12", - "@types/express": "4.17.13", - "@types/express-serve-static-core": "4.17.28", - "accepts": "^1.3.5", - "apollo-server-core": "^3.6.3", - "apollo-server-types": "^3.5.1", - "body-parser": "^1.19.0", - "cors": "^2.8.5", - "parseurl": "^1.3.3" - } - }, - "apollo-server-plugin-base": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.5.1.tgz", - "integrity": "sha512-wgDHz3lLrCqpecDky3z6AOQ0vik0qs0Cya/Ti6n3ESYXJ9MdK3jE/QunATIrOYYJaa+NKl9V7YwU+/bojNfFuQ==", - "requires": { - "apollo-server-types": "^3.5.1" - } - }, - "apollo-server-types": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.1.tgz", - "integrity": "sha512-zG7xLl4mmHuZMAYOfjWKHY/IC/GgIkJ3HnYuR7FRrnPpRA9Yt5Kf1M1rjm1Esuqzpb/dt8pM7cX40QaIQObCYQ==", - "requires": { - "apollo-reporting-protobuf": "^3.3.0", - "apollo-server-caching": "^3.3.0", - "apollo-server-env": "^4.2.1" - } - }, "arango-tools": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.6.0.tgz", @@ -17176,26 +16058,53 @@ "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": "7.6.1", - "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.6.1.tgz", - "integrity": "sha512-GfZQykBwBdRl32/RGy1NLIq8LTYW+PW6uU10B5uuZe5gIJpo8YLQhJBu6XwGfEyQrpb4CEMhmK1SgqdRgFnSxA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-10.2.2.tgz", + "integrity": "sha512-3Xllq5inTGjros0mBP9NFxrIW8Di0ldtFurLdrXy5z4NDVJPyJtnwUiiGrMPY21NuVu53wUDE23YN50jnX4epw==", "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" + "@types/node": "^20.11.26" } }, - "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", @@ -17208,7 +16117,7 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "array-includes": { "version": "3.1.4", @@ -17223,12 +16132,6 @@ "is-string": "^1.0.7" } }, - "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.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -17248,7 +16151,8 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true }, "assign-deep": { "version": "1.0.1", @@ -17263,12 +16167,6 @@ "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-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -17280,100 +16178,53 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "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 + "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": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.16.0", + "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": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", - "dev": true, - "requires": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.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 - }, + "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 - }, - "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" - } } } }, @@ -17387,28 +16238,25 @@ } }, "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" } }, "babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", + "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": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" } }, "babel-plugin-macros": { @@ -17478,33 +16326,36 @@ } }, "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==", + "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.8.3", - "@babel/plugin-syntax-import-meta": "^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.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.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-top-level-await": "^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": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", + "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": "^27.4.0", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" } }, "babel-runtime": { @@ -17531,11 +16382,6 @@ } } }, - "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", @@ -17548,17 +16394,6 @@ "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", @@ -17569,10 +16404,10 @@ "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==", + "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": { @@ -17587,20 +16422,22 @@ } }, "body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "requires": { - "bytes": "3.1.1", - "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.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" + "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.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "dependencies": { "debug": { @@ -17614,14 +16451,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "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", @@ -17629,36 +16466,24 @@ } }, "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" } }, - "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.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "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.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" } }, "bser": { @@ -17683,7 +16508,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==" }, "buffer-from": { "version": "1.1.2", @@ -17691,19 +16516,61 @@ "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.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" + "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/-/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": { @@ -17719,45 +16586,49 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001286", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz", - "integrity": "sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ==", + "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": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "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" + } + } } }, - "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, - "optional": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -17769,59 +16640,29 @@ "readdirp": "~3.6.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, - "optional": 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, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, - "optional": true, "requires": { "picomatch": "^2.2.1" } } } }, - "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.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "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": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "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 }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -17838,38 +16679,22 @@ "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==", + "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" } }, - "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==", + "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.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, @@ -17898,41 +16723,40 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "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==", + "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": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "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.3" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "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": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "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==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -17949,22 +16773,58 @@ "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==", + "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": { - "entities": "~2.0", - "mdurl": "~1.0.1", - "minimist": ">=1.2.2", - "string.prototype.repeat": "^0.2.0" + "mime-db": ">= 1.43.0 < 2" } }, - "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 + "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", @@ -17988,9 +16848,9 @@ } }, "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.8.0", @@ -18002,16 +16862,16 @@ } }, "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" }, "cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "requires": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" } }, @@ -18021,9 +16881,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "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": { @@ -18033,21 +16893,12 @@ "dev": true }, "core-js-compat": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.3.tgz", - "integrity": "sha512-c8M5h0IkNZ+I92QhIpuSijOxGAcj3lgpsWdkCqmUTZNwidujF4r3pi6x1DCN+Vcs5qTS2XWWMfWSuCqyupX8gw==", + "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.19.1", - "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 - } + "browserslist": "^4.22.2" } }, "cors": { @@ -18072,33 +16923,27 @@ "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==", + "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.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 - } + "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.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", @@ -18106,93 +16951,31 @@ "which": "^2.0.1" } }, - "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==", + "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": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" - }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, - "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 - }, "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true + "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", @@ -18201,9 +16984,9 @@ "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==", + "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": { @@ -18215,6 +16998,16 @@ "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", @@ -18227,13 +17020,7 @@ "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==" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "denque": { "version": "1.5.1", @@ -18241,14 +17028,14 @@ "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=" + "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.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", @@ -18257,51 +17044,22 @@ "dev": true }, "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "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" } }, - "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.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", - "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==", + "dev": true, "requires": { - "@babel/runtime": "^7.1.2" + "esutils": "^2.0.2" } }, "dom-walk": { @@ -18309,23 +17067,6 @@ "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 - } - } - }, "dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", @@ -18339,6 +17080,22 @@ "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", @@ -18350,18 +17107,18 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.17", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.17.tgz", - "integrity": "sha512-zhk1MravPtq/KBhmGB7TLBILmXTgRG9TFSI3qS3DbgyfHzIl72iiTE37r/BHIbPCJJlWIo5rySyxiH4vWhu2ZA==", + "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.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true }, "emoji-regex": { @@ -18370,59 +17127,10 @@ "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" - } - } - } - }, - "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==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "error-ex": { "version": "1.3.2", @@ -18461,12 +17169,34 @@ "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-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", @@ -18484,218 +17214,176 @@ "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.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "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.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "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" - } - } - } + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true }, "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", + "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.0.1", + "debug": "^4.3.2", "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", + "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", - "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", + "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", - "js-yaml": "^3.13.1", + "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.0.4", + "minimatch": "^3.1.2", "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" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "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==", + "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": { - "@babel/highlight": "^7.10.4" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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": { - "color-convert": "^2.0.1" + "is-glob": "^4.0.3" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "type-fest": "^0.20.2" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "argparse": "^2.0.1" } }, - "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==", + "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": { - "type-fest": "^0.20.2" + "p-locate": "^5.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 - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "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": { - "lru-cache": "^6.0.0" + "yocto-queue": "^0.1.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==", + "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": { - "has-flag": "^4.0.0" + "p-limit": "^3.0.2" } }, "type-fest": { @@ -18706,6 +17394,23 @@ } } }, + "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", @@ -18714,9 +17419,9 @@ "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==", + "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": {} }, @@ -18811,14 +17516,15 @@ } } }, - "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==", + "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-utils": "^2.0.0", - "regexpp": "^3.0.0" + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" } }, "eslint-plugin-import": { @@ -18869,93 +17575,88 @@ } }, "eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", + "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/experimental-utils": "^4.0.1" + "@typescript-eslint/utils": "^8.0.0" } }, - "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==", + "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-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" + "@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": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "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": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", - "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "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": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "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": "^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 - } + "estraverse": "^5.2.0" } }, "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==", + "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": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "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": "^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 - } + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -18965,20 +17666,12 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "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" - }, - "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": { @@ -18988,20 +17681,12 @@ "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==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, "esutils": { @@ -19013,19 +17698,7 @@ "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 + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "execa": { "version": "5.1.1", @@ -19044,56 +17717,59 @@ "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=", + "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": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" + "@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.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.1", - "content-disposition": "0.5.4", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.1", - "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", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.9.6", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -19107,10 +17783,24 @@ "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "safe-buffer": { "version": "5.2.1", @@ -19128,57 +17818,22 @@ "bcp47": "^1.1.2" } }, - "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" - } - }, "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": { - "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" - } - } - } - }, "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", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fast-safe-stringify": { @@ -19191,71 +17846,18 @@ "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.18", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz", - "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==", + "dev": true, "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.30" - }, - "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" - } - } + "reusify": "^1.0.4" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "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": { - "escape-string-regexp": "^1.0.5" + "bser": "2.1.1" } }, "file-entry-cache": { @@ -19267,47 +17869,26 @@ "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": "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" } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "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=" - } + "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": { @@ -19342,45 +17923,63 @@ } }, "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "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==" + "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==" }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "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": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "requires": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" - }, - "dependencies": { - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true - } + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" } }, "forwarded": { @@ -19391,19 +17990,7 @@ "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" - } + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-readdir-recursive": { "version": "1.1.0", @@ -19418,29 +18005,16 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "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.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 + "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", @@ -19455,14 +18029,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-package-type": { @@ -19471,6 +18051,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": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -19487,6 +18076,15 @@ "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", @@ -19510,13 +18108,6 @@ "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", @@ -19532,52 +18123,27 @@ "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" - } + "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.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "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": "15.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", - "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==" + "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", @@ -19587,61 +18153,61 @@ "arrify": "^1.0.1" } }, - "graphql-playground-html": { - "version": "1.6.30", - "resolved": "https://registry.npmjs.org/graphql-playground-html/-/graphql-playground-html-1.6.30.tgz", - "integrity": "sha512-tpCujhsJMva4aqE8ULnF7/l3xw4sNRZcSHu+R00VV+W0mfp+Q20Plvcrp+5UXD+2yS6oyCXncA+zoQJQqhGCEw==", - "requires": { - "xss": "^1.0.6" - } - }, - "graphql-playground-middleware-express": { - "version": "1.7.23", - "resolved": "https://registry.npmjs.org/graphql-playground-middleware-express/-/graphql-playground-middleware-express-1.7.23.tgz", - "integrity": "sha512-M/zbTyC1rkgiQjFSgmzAv6umMHOphYLNWZp6Ye5QrD77WfGOOoSqDsVmGUczc2pDkEPEzzGB/bvBO5rdzaTRgw==", - "requires": { - "graphql-playground-html": "^1.6.30" - } - }, "graphql-redis-subscriptions": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.4.2.tgz", - "integrity": "sha512-zMd1G6uZcEvqHZ6PsQ63BfRtw5Bg4cccql1pfkEQsj1vXVWe4p+o8DqNfd9DDUFXIqhHpqkSeN/49BlIwud4Ag==", + "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": "^4.17.3", - "iterall": "^1.3.0" + "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.9.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.9.0.tgz", - "integrity": "sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==", + "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.14.1", - "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.14.1.tgz", - "integrity": "sha512-IrJ2SI9IkCmWHyr7yIvtPNGWTWF3eTS+iNnw1DQMmEtsOgs1dUmT0ge+8M1+1xm+q3/5ZqB95yUYyThDyOTE+Q==", + "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.3.0" + "tslib": "^2.5.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==", + "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-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==", - "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", @@ -19650,22 +18216,6 @@ "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", @@ -19687,40 +18237,33 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "has-symbols": { + "has-property-descriptors": { "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-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.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, + "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.2" + "has-symbols": "^1.0.3" } }, - "hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "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==", + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "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==" - } + "function-bind": "^1.1.2" } }, "homedir-polyfill": { @@ -19732,15 +18275,6 @@ "parse-passwd": "^1.0.0" } }, - "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", @@ -19748,36 +18282,15 @@ "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - } - }, - "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, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "requires": { - "agent-base": "6", - "debug": "4" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" } }, "human-signals": { @@ -19786,11 +18299,6 @@ "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", @@ -19806,9 +18314,15 @@ "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==", + "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": { @@ -19830,9 +18344,9 @@ } }, "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "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", @@ -19856,14 +18370,6 @@ "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", @@ -19879,78 +18385,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "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", @@ -19985,22 +18419,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "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", @@ -20016,29 +18434,46 @@ "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" + "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.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true + "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.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "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": { - "has": "^1.0.3" + "hasown": "^2.0.2" } }, "is-date-object": { @@ -20050,12 +18485,6 @@ "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-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -20088,11 +18517,6 @@ "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-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -20120,20 +18544,21 @@ "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-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", @@ -20174,11 +18599,13 @@ "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-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", @@ -20195,6 +18622,11 @@ "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", @@ -20204,7 +18636,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "isomorphic-fetch": { "version": "3.0.0", @@ -20216,32 +18649,40 @@ } }, "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "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": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", + "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.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "semver": "^7.5.4" + }, + "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.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "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": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "dependencies": { @@ -20252,14 +18693,20 @@ "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==", + "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": "^6.0.0" + "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", @@ -20272,28 +18719,20 @@ } }, "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.23", "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-lib-coverage": "^3.0.0" } }, "istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", + "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", @@ -20305,483 +18744,320 @@ "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==" }, - "jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", - "dev": true, - "requires": { - "@jest/core": "^27.4.7", - "import-local": "^3.0.2", - "jest-cli": "^27.4.7" - } - }, - "jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "execa": "^5.0.0", - "throat": "^6.0.1" - } - }, - "jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", + "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": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", - "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" - } - } + "@isaacs/cliui": "^8.0.2" } }, - "jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", + "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": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "prompts": "^2.0.1", - "yargs": "^16.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" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "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.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "leven": "^3.1.0", - "pretty-format": "^27.4.6" - } - }, - "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": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" } }, - "jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", + "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": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" + "execa": "^5.1.1", + "jest-util": "30.3.0", + "p-limit": "^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": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "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.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "leven": "^3.1.0", - "pretty-format": "^27.4.6" - } - }, - "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==", + "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": { - "has-flag": "^4.0.0" + "yocto-queue": "^0.1.0" } } } }, - "jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "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": { - "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "@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": { - "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==", + "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": { - "has-flag": "^4.0.0" + "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-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", + "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": { - "detect-newline": "^3.0.0" + "@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-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" + "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": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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": { - "color-convert": "^2.0.1" + "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" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.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==", + "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": { - "color-name": "~1.1.4" + "@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" } }, - "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==", + "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 }, - "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 + "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" + } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "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-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", + "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/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", - "jsdom": "^16.6.0" + "@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": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", + "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": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "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": { @@ -20794,377 +19070,144 @@ "promise-polyfill": "^8.1.3" } }, - "jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", - "dev": true - }, "jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", + "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": "^27.4.2", - "@types/graceful-fs": "^4.1.2", + "@jest/types": "30.3.0", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "dependencies": { - "micromatch": { + "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/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - } - } - }, - "jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.4.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.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==", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "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.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", + "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": "^27.4.0", - "pretty-format": "^27.4.6" + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" } }, "jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "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": { - "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.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" - } - } + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" } }, "jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", + "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.3" + "stack-utils": "^2.0.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 - }, - "micromatch": { + "picomatch": { "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" - } + "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 - }, - "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-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", + "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": "^27.4.2", - "@types/node": "*" + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-util": "30.3.0" } }, "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==", + "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": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", + "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": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", + "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": { - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.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": { - "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.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "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.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "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/types": "^27.4.2", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", + "@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": "^27.4.6" + "pretty-format": "30.3.0" } }, "slash": { @@ -21172,548 +19215,319 @@ "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.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", + "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/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" } }, "jest-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", + "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": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "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==", + "@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": { - "color-name": "~1.1.4" + "yocto-queue": "^0.1.0" } }, - "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==", + "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==", + "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": { - "has-flag": "^4.0.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } } } }, "jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", + "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": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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": { - "color-convert": "^2.0.1" + "balanced-match": "^1.0.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" } }, - "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==", + "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 }, - "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 + "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 - }, - "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.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, "jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@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.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", - "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", - "semver": "^7.3.2" + "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": { - "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" - } + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true } } }, "jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", + "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": "^27.4.2", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", - "picomatch": "^2.2.3" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.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" - } - }, - "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==", + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "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==", + "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": "^26.6.2", - "camelcase": "^6.0.0", + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^26.6.2" + "pretty-format": "^29.7.0" }, "dependencies": { "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "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": "^15.0.0", + "@types/yargs": "^17.0.8", "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.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", - "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", - "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==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "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==", + "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": "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==", + "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": { - "has-flag": "^4.0.0" + "@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": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", + "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": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.4.2", - "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" - } - } + "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": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "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.0.0" + "supports-color": "^8.1.1" }, "dependencies": { "has-flag": { @@ -21733,77 +19547,38 @@ } } }, + "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.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "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" } }, - "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.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - } - } - }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "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-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, - "peer": 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", @@ -21828,28 +19603,15 @@ "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" - } + "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": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "requires": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -21860,104 +19622,35 @@ "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.5.4" }, "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=", + "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": { - "loose-envify": "^1.0.0" + "lru-cache": "^6.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==", + "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", + "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==", "requires": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -21967,12 +19660,6 @@ "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 - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -21995,24 +19682,6 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "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", @@ -22022,17 +19691,6 @@ "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", @@ -22058,7 +19716,7 @@ "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, "lodash.isarguments": { "version": "3.1.0", @@ -22068,27 +19726,27 @@ "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "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": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "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": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "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": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "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": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "lodash.merge": { "version": "4.6.2", @@ -22099,18 +19757,12 @@ "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "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 + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" }, "log-symbols": { "version": "4.1.0", @@ -22120,57 +19772,6 @@ "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": { @@ -22210,19 +19811,13 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "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": "7.1.0", "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.1.0.tgz", @@ -22237,20 +19832,20 @@ "tmpl": "1.0.5" } }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + "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=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "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", @@ -22258,30 +19853,19 @@ "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==", + "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": { @@ -22290,25 +19874,16 @@ "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" - } + "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.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "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.49.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -22318,63 +19893,68 @@ "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==", "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==", + "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 }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "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.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" }, - "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" - } + "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==" }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "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", @@ -22382,16 +19962,17 @@ "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": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, - "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 + "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", @@ -22404,9 +19985,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -22440,46 +20021,62 @@ } } }, - "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=", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "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 }, - "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==" + "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": "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==", + "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": "^0.21.1", - "jsonwebtoken": "^8.2.1", - "underscore": "^1.9.0" + "axios": "^1.7.2", + "jsonwebtoken": "^9.0.2" } }, "npm-run-path": { @@ -22491,22 +20088,15 @@ "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-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 + "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", @@ -22549,13 +20139,18 @@ } }, "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.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", @@ -22575,9 +20170,9 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "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", @@ -22585,7 +20180,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "ora": { @@ -22603,65 +20198,8 @@ "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-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -22691,10 +20229,10 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "papaparse": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz", - "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==", + "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": { @@ -22729,12 +20267,6 @@ "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", @@ -22764,10 +20296,28 @@ "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.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", @@ -22776,15 +20326,15 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "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.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "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": { @@ -22794,9 +20344,9 @@ "dev": true }, "pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "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": { @@ -22844,67 +20394,16 @@ } } }, - "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==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.4.tgz", + "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==", "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==" + "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", @@ -22919,16 +20418,31 @@ "dev": true }, "pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "@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", @@ -22942,53 +20456,12 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, - "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.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "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", @@ -22998,19 +20471,32 @@ "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": "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" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + } } }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "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": { @@ -23019,10 +20505,19 @@ "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.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "requires": { + "side-channel": "^1.1.0" + } }, "queue-microtask": { "version": "1.2.3", @@ -23030,93 +20525,38 @@ "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.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "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.1", - "http-errors": "1.8.1", - "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" } }, "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==", + "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", - "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" + "loose-envify": "^1.1.0" } }, "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==", + "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 }, - "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" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -23128,24 +20568,13 @@ "util-deprecate": "^1.0.1" } }, - "recompose": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", - "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", + "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": { - "@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==" - } + "picomatch": "^2.2.1" } }, "redis-commands": { @@ -23184,7 +20613,8 @@ "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==" + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true }, "regenerator-transform": { "version": "0.14.5", @@ -23195,12 +20625,6 @@ "@babel/runtime": "^7.8.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", @@ -23241,23 +20665,18 @@ "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==", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "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.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-cwd": { @@ -23275,10 +20694,10 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "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": { @@ -23311,12 +20730,6 @@ "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", @@ -23326,23 +20739,6 @@ "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", @@ -23353,66 +20749,30 @@ "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==", + "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.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "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": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", + "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": "1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "~2.0.2" }, "dependencies": { "debug": { @@ -23426,42 +20786,35 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, - "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.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "~0.19.1" } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + "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", @@ -23469,12 +20822,20 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "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.1", - "safe-buffer": "^5.0.1" + "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": { @@ -23502,14 +20863,47 @@ "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, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "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": { @@ -23518,55 +20912,29 @@ "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": { + "simple-update-notifier": { "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==", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "semver": "^7.5.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" - } - }, - "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==", + "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", @@ -23594,7 +20962,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "stable": { @@ -23603,20 +20971,12 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "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" - }, - "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": { @@ -23625,9 +20985,9 @@ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "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", @@ -23657,20 +21017,26 @@ } }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "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.0" + "strip-ansi": "^6.0.1" } }, - "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-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", @@ -23693,273 +21059,108 @@ } }, "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.11.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", - "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", - "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": "7.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.0.2.tgz", - "integrity": "sha512-2Kx35bZxLLJMBKtuXezxvD0aZQ7l923VwoCn7EtUx+aFxdG7co7PeRIddfrNtvvMuGaLZXA0mKzX+yWRhjrJ7A==", - "dev": true, - "requires": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.3", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "^2.5.0", - "qs": "^6.10.1", - "readable-stream": "^3.6.0", - "semver": "^7.3.5" - }, - "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "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.2.1", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.1.tgz", - "integrity": "sha512-2kBKhfZgnPLmjpzB0n7A2ZnEAWTaLXq4bn3EEVY9w8rUpLyIlSusqKKvWA1Cav7hxXBnXGpxBsSeOHj5wQGe1Q==", - "dev": true, - "requires": { - "methods": "^1.1.2", - "superagent": "^7.0.2" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ansi-regex": "^5.0.1" } }, - "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==", + "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": { - "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" - } - } + "ansi-regex": "^5.0.1" } }, - "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==" + "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 }, - "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==" + "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 }, - "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==", + "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 }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "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": { - "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" + "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": { - "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "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": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "lru-cache": "^6.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 } } }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": 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==", + "supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", "dev": true, "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" + "methods": "^1.1.2", + "superagent": "^9.0.1" } }, - "terser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz", - "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==", + "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, - "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 - } + "has-flag": "^3.0.0" } }, - "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==", + "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, - "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 - } + "@pkgr/core": "^0.2.9" } }, "test-exclude": { @@ -23979,30 +21180,29 @@ "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==", + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "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": { @@ -24011,11 +21211,22 @@ "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-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", @@ -24031,47 +21242,18 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, - "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" - } + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "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": { - "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" - } + "requires": {} }, "tsconfig-paths": { "version": "3.12.0", @@ -24086,9 +21268,9 @@ }, "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" @@ -24103,26 +21285,14 @@ } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "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 - } - } + "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", @@ -24154,27 +21324,23 @@ "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, + "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": { - "is-typedarray": "^1.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" } }, "typescript": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", - "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", + "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 }, - "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==" - }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -24187,10 +21353,16 @@ "which-boxed-primitive": "^1.0.2" } }, - "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" + "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", @@ -24220,16 +21392,53 @@ "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", "dev": true }, - "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=" + "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", @@ -24254,34 +21463,28 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "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-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 + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==" }, "v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "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": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^2.0.0" }, "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==", + "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 } } @@ -24296,43 +21499,15 @@ } }, "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" - }, - "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==" + "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=" }, - "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.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -24350,17 +21525,6 @@ "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", @@ -24370,100 +21534,11 @@ "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", @@ -24486,10 +21561,24 @@ "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.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": { @@ -24501,32 +21590,17 @@ "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 - } + } + }, + "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": { @@ -24536,23 +21610,23 @@ "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==", + "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", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "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 + } } }, - "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", @@ -24569,34 +21643,6 @@ "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", @@ -24620,38 +21666,31 @@ "dev": true }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "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": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" } }, "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==", + "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, - "peer": true + "dev": true } } } diff --git a/api/package.json b/api/package.json index d60f5ff5e9..a2aa3eec11 100644 --- a/api/package.json +++ b/api/package.json @@ -11,8 +11,9 @@ "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.*", + "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", @@ -21,64 +22,69 @@ "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": { - "@lingui/core": "^3.13.0", - "apollo-server": "^3.6.3", - "apollo-server-core": "^3.6.3", - "apollo-server-express": "^3.6.3", + "@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": "^7.6.1", + "arangojs": "^10.2.2", "bcryptjs": "^2.4.3", - "cookie-parser": "^1.4.6", + "body-parser": "^1.20.5", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dataloader": "^2.0.0", "dotenv-safe": "^8.2.0", - "express": "^4.17.2", + "express": "^4.22.2", "express-request-language": "^1.1.15", - "graphql": "^15.8.0", + "graphql": "^16.12.0", "graphql-depth-limit": "^1.1.0", - "graphql-playground-middleware-express": "^1.7.23", - "graphql-redis-subscriptions": "^2.4.2", - "graphql-relay": "^0.9.0", - "graphql-scalars": "^1.14.1", - "graphql-subscriptions": "^1.2.1", + "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", - "graphql-voyager": "^1.0.0-rc.31", "ioredis": "^4.28.3", "isomorphic-fetch": "^3.0.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.2", "make-plural": "^7.1.0", - "moment": "^2.29.1", - "notifications-node-client": "^5.1.0", - "subscriptions-transport-ws": "^0.11.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.7.0" + "uuid": "^11.1.1", + "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": "^27.4.6", - "@lingui/cli": "^3.13.0", - "@lingui/loader": "^3.13.0", - "@lingui/macro": "^3.13.0", + "@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": "^27.4.6", + "babel-jest": "^30.3.0", "babel-plugin-macros": "^3.1.0", "babel-polyfill": "^6.26.0", - "eslint": "^7.32.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", + "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^24.7.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.2.0", - "jest": "^27.4.7", + "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": "^27.4.6", + "jest-matcher-utils": "^30.3.0", + "nodemon": "^3.1.11", "prettier": "^2.5.1", - "supertest": "^6.2.1" + "supertest": "^7.0.0" } } diff --git a/api/src/__tests__/create-context.test.js b/api/src/__tests__/create-context.test.js index 0ac1c6ee7f..d4d9b39be5 100644 --- a/api/src/__tests__/create-context.test.js +++ b/api/src/__tests__/create-context.test.js @@ -1,5 +1,5 @@ -const { createContext } = require('../create-context.js') -const { tokenize } = require('../auth') +const {createContext} = require('../create-context.js') +const {tokenize} = require('../auth') describe('given the create context function', () => { describe('request authorization token is not set', () => { @@ -8,7 +8,7 @@ describe('given the create context function', () => { query: jest.fn(), transaction: jest.fn(), collections: [], - req: { headers: {}, language: 'en' }, + req: {headers: {}, language: 'en'}, res: {}, }) @@ -25,7 +25,7 @@ describe('given the create context function', () => { req: { language: 'en', headers: { - authorization: tokenize({ parameters: { userKey: '1234' } }), + authorization: tokenize({parameters: {userKey: '1234'}}), }, }, res: {}, diff --git a/api/src/__tests__/initialize-loaders.test.js b/api/src/__tests__/initialize-loaders.test.js index e60ac02138..923f4ab5f7 100644 --- a/api/src/__tests__/initialize-loaders.test.js +++ b/api/src/__tests__/initialize-loaders.test.js @@ -13,9 +13,6 @@ describe('initializeLoaders', () => { }) expect(loaders).toHaveProperty( - 'loadChartSummaryByKey', - 'loadAggregateGuidanceTagByTagId', - 'loadAggregateGuidanceTagConnectionsByTagId', 'loadDkimFailConnectionsBySumId', 'loadDmarcFailConnectionsBySumId', 'loadDmarcSummaryConnectionsByUserId', @@ -29,32 +26,7 @@ describe('initializeLoaders', () => { 'loadDomainByKey', 'loadDomainConnectionsByOrgId', 'loadDomainConnectionsByUserId', - 'loadDkimByKey', - 'loadDkimResultByKey', - 'loadDmarcByKey', - 'loadSpfByKey', - 'loadDkimConnectionsByDomainId', - 'loadDkimResultConnectionsByDkimId', - 'loadDmarcConnectionsByDomainId', - 'loadSpfConnectionsByDomainId', - 'loadHttpsByKey', - 'loadHttpsConnectionsByDomainId', - 'loadSslByKey', - 'loadSslConnectionByDomainId', - 'loadDkimGuidanceTagByTagId', - 'loadDkimGuidanceTagConnectionsByTagId', - 'loadDmarcGuidanceTagByTagId', - 'loadDmarcGuidanceTagConnectionsByTagId', - 'loadHttpsGuidanceTagByTagId', - 'loadHttpsGuidanceTagConnectionsByTagId', - 'loadSpfGuidanceTagByTagId', - 'loadSpfGuidanceTagConnectionsByTagId', - 'loadSslGuidanceTagByTagId', - 'loadSslGuidanceTagConnectionsByTagId', 'loadOrgByKey', - 'loadOrgBySlug', - 'loadOrgConnectionsByDomainId', - 'loadOrgConnectionsByUserId', 'loadUserByUserName', 'loadUserByKey', 'loadAffiliationByKey', diff --git a/api/src/__tests__/server.test.js b/api/src/__tests__/server.test.js index 2c23b76969..94f16e71d2 100644 --- a/api/src/__tests__/server.test.js +++ b/api/src/__tests__/server.test.js @@ -1,6 +1,7 @@ import request from 'supertest' import { Server } from '../server' -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../testUtilities' import dbschema from '../../database.json' const { @@ -79,10 +80,8 @@ describe('parse server', () => { objectCost, listFactor, tracing: false, - context: { - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), + context: () => { + return { query: jest.fn(), collections: jest.fn(), transaction: jest.fn() } }, }), ) @@ -103,10 +102,8 @@ describe('parse server', () => { scalarCost: 100, objectCost: 100, listFactor: 100, - context: { - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), + context: () => { + return { query: jest.fn(), collections: jest.fn(), transaction: jest.fn() } }, }), ) @@ -115,9 +112,7 @@ describe('parse server', () => { .send({ query: '{__schema {types {kind}}}' }) expect(response.status).toEqual(400) - expect(response.text).toEqual( - expect.stringContaining('Query error, query is too complex.'), - ) + expect(response.text).toEqual(expect.stringContaining('Query error, query is too complex.')) }) }) @@ -130,10 +125,8 @@ describe('parse server', () => { scalarCost: 1, objectCost: 1, listFactor: 1, - context: { - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), + context: () => { + return { query: jest.fn(), collections: jest.fn(), transaction: jest.fn() } }, }), ) @@ -144,9 +137,7 @@ describe('parse server', () => { }) expect(response.status).toEqual(400) - expect(response.text).toEqual( - expect.stringContaining('exceeds maximum operation depth'), - ) + 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/src/affiliation/inputs/__tests__/affiliation-org-order.test.js b/api/src/affiliation/inputs/__tests__/affiliation-org-order.test.js index d0de8dc68a..09ebd154d3 100644 --- a/api/src/affiliation/inputs/__tests__/affiliation-org-order.test.js +++ b/api/src/affiliation/inputs/__tests__/affiliation-org-order.test.js @@ -9,17 +9,13 @@ describe('given the affiliationOrder input object', () => { const demoType = affiliationOrgOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(AffiliationOrgOrderField), - ) + 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 index bc2d046be7..c01050edc4 100644 --- a/api/src/affiliation/inputs/__tests__/affiliation-user-order.test.js +++ b/api/src/affiliation/inputs/__tests__/affiliation-user-order.test.js @@ -9,17 +9,13 @@ describe('given the affiliationOrder input object', () => { const demoType = affiliationUserOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(AffiliationUserOrderField), - ) + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(AffiliationUserOrderField)) }) }) }) diff --git a/api/src/affiliation/inputs/affiliation-org-order.js b/api/src/affiliation/inputs/affiliation-org-order.js index b2ded8a1ff..f820407cb7 100644 --- a/api/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/src/affiliation/inputs/affiliation-user-order.js b/api/src/affiliation/inputs/affiliation-user-order.js index 780921c7f2..31525d117b 100644 --- a/api/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/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js b/api/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js index d696644c92..d6649a47e0 100644 --- a/api/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js +++ b/api/src/affiliation/loaders/__tests__/load-affiliation-by-key.test.js @@ -1,4 +1,5 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +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' @@ -25,23 +26,22 @@ describe('given a loadAffiliationByKey dataloader', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -172,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', @@ -184,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([ @@ -211,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([ @@ -239,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', @@ -252,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.`), ) } @@ -281,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/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 index e178fb4100..b303e69591 100644 --- a/api/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,21 +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 { 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 describe('given the load affiliations by org id function', () => { - let query, drop, truncate, collections, user, org, userTwo, i18n + let query, drop, truncate, collections, db, user, org, userTwo, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -30,30 +28,28 @@ describe('given the load affiliations by org id function', () => { 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, - })) + ;({ query, drop, truncate, collections, db } = 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', - 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, }) @@ -123,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 @@ -153,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), }, } @@ -177,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 @@ -197,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], }, @@ -210,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), }, } @@ -234,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 @@ -253,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], }, @@ -266,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), }, } @@ -290,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 @@ -309,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], }, @@ -322,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), }, } @@ -356,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 @@ -376,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), }, } @@ -413,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 @@ -433,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), }, } @@ -476,18 +403,6 @@ describe('given the load affiliations by org id function', () => { }) orgOne = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'slug-org-a', @@ -511,6 +426,12 @@ describe('given the load affiliations by org id function', () => { }, }, }) + const orgOneSummary = await collections.organizationSummaries.save({ + organization: orgOne._id, + web: { pass: 50, fail: 1000, total: 1050 }, + mail: { pass: 50, fail: 1000, total: 1050 }, + }) + await db.collection('organizations').update(orgOne._key, { latestSummaryId: orgOneSummary._id }) affOne = await collections.affiliations.save({ _key: '1', _from: orgOne._id, @@ -530,7 +451,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({ @@ -552,7 +473,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', }, } @@ -601,7 +522,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', }, } @@ -696,9 +617,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.'), ) } @@ -755,11 +674,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.`, @@ -785,11 +700,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.`, @@ -862,9 +773,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, @@ -881,11 +790,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: ${ @@ -897,9 +802,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, @@ -916,11 +819,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: ${ @@ -935,11 +834,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, @@ -954,9 +849,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([ @@ -988,9 +881,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([ @@ -1092,9 +983,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([ @@ -1121,11 +1010,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.`, @@ -1198,9 +1083,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, @@ -1218,9 +1101,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([ @@ -1233,9 +1114,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, @@ -1253,9 +1132,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([ @@ -1271,11 +1148,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, @@ -1290,11 +1163,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([ @@ -1326,11 +1195,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/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 index ef90f0aaf2..2d6ca85ec7 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,7 +13,7 @@ import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the load affiliations by user id function', () => { - let query, drop, truncate, collections, user, orgOne, orgTwo, i18n + let query, drop, truncate, collections, db, user, orgOne, orgTwo, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -30,23 +31,22 @@ describe('given the load affiliations by user id function', () => { describe('given there are user affiliations to be returned', () => { let affOne, affTwo beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -127,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 @@ -147,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], }, @@ -160,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), }, } @@ -203,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], }, @@ -216,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), }, } @@ -258,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], }, @@ -271,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), }, } @@ -313,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], }, @@ -326,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), }, } @@ -380,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], }, @@ -393,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), }, } @@ -436,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], }, @@ -449,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), }, } @@ -494,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], }, @@ -507,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), }, } @@ -550,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], }, @@ -563,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), }, } @@ -580,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({ @@ -597,18 +513,6 @@ describe('given the load affiliations by user id function', () => { }) orgOne = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'slug-org-a', @@ -632,20 +536,15 @@ describe('given the load affiliations by user id function', () => { }, }, }) + const orgOneSummary = await collections.organizationSummaries.save({ + organization: orgOne._id, + web: { pass: 50, fail: 1000, total: 1050 }, + mail: { pass: 50, fail: 1000, total: 1050 }, + }) + await db.collection('organizations').update(orgOne._key, { latestSummaryId: orgOneSummary._id }) + orgTwo = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, orgDetails: { en: { slug: 'slug-org-b', @@ -669,20 +568,15 @@ describe('given the load affiliations by user id function', () => { }, }, }) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + web: { pass: 51, fail: 1001, total: 1052 }, + mail: { pass: 51, fail: 1001, total: 1052 }, + }) + await db.collection('organizations').update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) + orgThree = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, orgDetails: { en: { slug: 'slug-org-c', @@ -706,6 +600,12 @@ describe('given the load affiliations by user id function', () => { }, }, }) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + web: { pass: 52, fail: 1002, total: 1054 }, + mail: { pass: 52, fail: 1002, total: 1054 }, + }) + await db.collection('organizations').update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) affOne = await collections.affiliations.save({ _key: '1', _from: orgOne._id, @@ -1302,9 +1202,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, @@ -1354,9 +1252,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, @@ -1404,9 +1300,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, @@ -1456,9 +1350,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, @@ -1506,9 +1398,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, @@ -1558,9 +1448,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, @@ -1608,9 +1496,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, @@ -1660,9 +1546,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, @@ -1710,9 +1594,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, @@ -1762,9 +1644,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, @@ -1812,9 +1692,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, @@ -1864,9 +1742,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, @@ -1914,9 +1790,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, @@ -1966,9 +1840,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, @@ -2016,9 +1888,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, @@ -2068,9 +1938,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, @@ -2118,9 +1986,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, @@ -2170,9 +2036,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, @@ -2220,9 +2084,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, @@ -2272,9 +2134,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, @@ -2322,9 +2182,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, @@ -2376,9 +2234,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, @@ -2426,9 +2282,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, @@ -2478,9 +2332,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, @@ -2528,9 +2380,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, @@ -2580,9 +2430,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, @@ -2630,9 +2478,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, @@ -2682,9 +2528,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, @@ -2732,9 +2576,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, @@ -2784,9 +2626,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, @@ -2834,9 +2674,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, @@ -2886,9 +2724,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, @@ -2936,9 +2772,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, @@ -2988,9 +2822,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, @@ -3038,9 +2870,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, @@ -3090,9 +2920,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, @@ -3140,9 +2968,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, @@ -3192,9 +3018,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, @@ -3242,9 +3066,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, @@ -4050,9 +3872,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.'), ) } @@ -4111,11 +3931,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.`, @@ -4142,11 +3958,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.`, @@ -4221,9 +4033,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', @@ -4241,11 +4051,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: ${ @@ -4257,9 +4063,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', @@ -4277,11 +4081,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: ${ @@ -4296,11 +4096,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, @@ -4319,9 +4115,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([ @@ -4357,9 +4151,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([ @@ -4467,11 +4259,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.`, @@ -4498,11 +4286,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.`, @@ -4577,9 +4361,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', @@ -4598,9 +4380,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([ @@ -4613,9 +4393,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', @@ -4634,9 +4412,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([ @@ -4652,11 +4428,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, @@ -4672,11 +4444,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([ @@ -4709,11 +4477,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/src/affiliation/loaders/load-affiliation-by-key.js b/api/src/affiliation/loaders/load-affiliation-by-key.js index 1681a28305..de45608d6a 100644 --- a/api/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 index d36a129ef2..578943de5a 100644 --- a/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js +++ b/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js @@ -4,7 +4,7 @@ import { t } from '@lingui/macro' export const loadAffiliationConnectionsByOrgId = ({ query, userKey, cleanseInput, i18n }) => - async ({ orgId, after, before, first, last, orderBy, search }) => { + async ({ orgId, after, before, first, last, orderBy, search, includePending }) => { let afterTemplate = aql`` if (typeof after !== 'undefined') { const { id: afterId } = fromGlobalId(cleanseInput(after)) @@ -20,9 +20,15 @@ export const loadAffiliationConnectionsByOrgId = let affiliationField, documentField /* istanbul ignore else */ - if (orderBy.field === 'user-username') { + 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` @@ -48,9 +54,15 @@ export const loadAffiliationConnectionsByOrgId = let affiliationField, documentField /* istanbul ignore else */ - if (orderBy.field === 'user-username') { + 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` @@ -67,18 +79,14 @@ export const loadAffiliationConnectionsByOrgId = `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.`, - ), + 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.`, - ), + 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 */ @@ -87,11 +95,7 @@ export const loadAffiliationConnectionsByOrgId = 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.`, - ), - ) + 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 @@ -114,9 +118,7 @@ export const loadAffiliationConnectionsByOrgId = 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}\`.`), - ) + 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)` @@ -134,10 +136,18 @@ export const loadAffiliationConnectionsByOrgId = let affField, hasNextPageDocument, hasPreviousPageDocument /* istanbul ignore else */ - if (orderBy.field === 'user-username') { + 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` @@ -156,8 +166,12 @@ export const loadAffiliationConnectionsByOrgId = let sortByField = aql`` if (typeof orderBy !== 'undefined') { /* istanbul ignore else */ - if (orderBy.field === 'user-username') { + 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},` } } @@ -174,19 +188,36 @@ export const loadAffiliationConnectionsByOrgId = search = cleanseInput(search) userSearchQuery = aql` LET tokenArr = TOKENS(${search}, "text_en") - LET userIds = UNIQUE( - FOR token IN tokenArr + LET searchedDisplayNamesCount = FLATTEN( + FOR tokenItem in tokenArr + LET token = LOWER(tokenItem) FOR user IN userSearch SEARCH ANALYZER( user.displayName LIKE CONCAT("%", token, "%") - OR user.userName LIKE CONCAT("%", token, "%") , "text_en") - RETURN user._id + 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` @@ -195,11 +226,20 @@ export const loadAffiliationConnectionsByOrgId = ${userSearchQuery} LET affiliationKeys = ( - FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations + 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 @@ -250,9 +290,7 @@ export const loadAffiliationConnectionsByOrgId = 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.`), - ) + throw new Error(i18n._(t`Unable to query affiliation(s). Please try again.`)) } let filteredAffiliations @@ -262,9 +300,7 @@ export const loadAffiliationConnectionsByOrgId = 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.`), - ) + throw new Error(i18n._(t`Unable to load affiliation(s). Please try again.`)) } if (filteredAffiliations.affiliations.length === 0) { 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 index f3b6c86424..d84fd6898b 100644 --- a/api/src/affiliation/loaders/load-affiliation-connections-by-user-id.js +++ b/api/src/affiliation/loaders/load-affiliation-connections-by-user-id.js @@ -1,358 +1,358 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' +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})` + ({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(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.pass` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).latestSummaryId).mail.pass` + } else if (orderBy.field === 'org-summary-mail-fail') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.fail` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).latestSummaryId).mail.fail` + } else if (orderBy.field === 'org-summary-mail-total') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.total` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).latestSummaryId).mail.total` + } else if (orderBy.field === 'org-summary-web-pass') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.pass` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).latestSummaryId).web.pass` + } else if (orderBy.field === 'org-summary-web-fail') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.fail` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).latestSummaryId).web.fail` + } else if (orderBy.field === 'org-summary-web-total') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.total` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).latestSummaryId).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(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.pass` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).latestSummaryId).mail.pass` + } else if (orderBy.field === 'org-summary-mail-fail') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.fail` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).latestSummaryId).mail.fail` + } else if (orderBy.field === 'org-summary-mail-total') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.total` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).latestSummaryId).mail.total` + } else if (orderBy.field === 'org-summary-web-pass') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.pass` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).latestSummaryId).web.pass` + } else if (orderBy.field === 'org-summary-web-fail') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.fail` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).latestSummaryId).web.fail` + } else if (orderBy.field === 'org-summary-web-total') { + affiliationField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.total` + documentField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._from).key).latestSummaryId).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 { - let afterTemplateDirection + 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') { - afterTemplateDirection = aql`>` + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` } else { - afterTemplateDirection = aql`<` + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` } - let affiliationField, documentField + let affField, hasNextPageDocument, hasPreviousPageDocument /* 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` + 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') { - 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` + 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') { - 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` + 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') { - 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` + 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') { - 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` + 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') { - 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` + 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') { - 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` + 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') { - 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` + 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') { - affiliationField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified` - documentField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._from).key).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') { - 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` + affField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.pass` + hasNextPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).latestSummaryId).mail.pass` + hasPreviousPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).latestSummaryId).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` + affField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.fail` + hasNextPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).latestSummaryId).mail.fail` + hasPreviousPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).latestSummaryId).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` + affField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.total` + hasNextPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).latestSummaryId).mail.total` + hasPreviousPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).latestSummaryId).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` + affField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.pass` + hasNextPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).latestSummaryId).web.pass` + hasPreviousPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).latestSummaryId).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` + affField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.fail` + hasNextPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).latestSummaryId).web.fail` + hasPreviousPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).latestSummaryId).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` + affField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.total` + hasNextPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._from).key).latestSummaryId).web.total` + hasPreviousPageDocument = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._from).key).latestSummaryId).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)` + 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)` } - afterTemplate = aql` - FILTER ${affiliationField} ${afterTemplateDirection} ${documentField} - OR (${affiliationField} == ${documentField} - AND TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})) - ` - } - } + hasNextPageFilter = aql` + FILTER ${affField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${affField} == ${hasNextPageDocument} + AND TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)) + ` - 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`>` - } + hasPreviousPageFilter = aql` + FILTER ${affField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${affField} == ${hasPreviousPageDocument} + AND TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)) + ` + } - let affiliationField, documentField + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { /* 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).acronym ${orderBy.direction},` } 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).name ${orderBy.direction},` } 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).slug ${orderBy.direction},` } 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).zone ${orderBy.direction},` } 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).sector ${orderBy.direction},` } 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).country ${orderBy.direction},` } 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).province ${orderBy.direction},` } 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` + sortByField = aql`TRANSLATE(${language}, DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).orgDetails).city ${orderBy.direction},` } 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` + sortByField = aql`DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).verified ${orderBy.direction},` } 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` + sortByField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.pass ${orderBy.direction},` } 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` + sortByField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.fail ${orderBy.direction},` } 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` + sortByField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).mail.total ${orderBy.direction},` } 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` + sortByField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.pass ${orderBy.direction},` } 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` + sortByField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.fail ${orderBy.direction},` } 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` + sortByField = aql`DOCUMENT(DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key).latestSummaryId).web.total ${orderBy.direction},` } 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)` + sortByField = aql`COUNT(FOR v, e IN 1..1 ANY DOCUMENT(organizations, PARSE_IDENTIFIER(affiliation._from).key)._id claims RETURN e._to) ${orderBy.direction},` } - - 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`<` + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + sortString = aql`ASC` } - 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 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 @@ -375,18 +375,18 @@ export const loadAffiliationConnectionsByUserId = ) LET orgIds = UNION_DISTINCT(searchOrgsEN, searchedOrgsFR) ` - orgIdFilter = aql`FILTER e._from IN orgIds` - } + orgIdFilter = aql`FILTER e._from IN orgIds` + } - let filteredAffiliationCursor - try { - filteredAffiliationCursor = await query` + let filteredAffiliationCursor + try { + filteredAffiliationCursor = await query` WITH affiliations, organizations, organizationSearch users ${orgSearchQuery} - + LET affiliationKeys = ( - FOR v, e IN 1..1 INBOUND ${userId} affiliations + FOR v, e IN 1..1 INBOUND ${userId} affiliations ${orgIdFilter} RETURN e._key ) @@ -429,55 +429,55 @@ export const loadAffiliationConnectionsByUserId = "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.`), - ) - } + } 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.`), - ) - } + 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: '', - }, + if (filteredAffiliations.affiliations.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } } - } - const edges = filteredAffiliations.affiliations.map((affiliation) => { + const edges = filteredAffiliations.affiliations.map((affiliation) => { + return { + cursor: toGlobalId('affiliation', affiliation._key), + node: affiliation, + } + }) + return { - cursor: toGlobalId('affiliation', affiliation._key), - node: affiliation, + edges, + totalCount: filteredAffiliations.totalCount, + pageInfo: { + hasNextPage: filteredAffiliations.hasNextPage, + hasPreviousPage: filteredAffiliations.hasPreviousPage, + startCursor: toGlobalId('affiliation', filteredAffiliations.startKey), + endCursor: toGlobalId('affiliation', filteredAffiliations.endKey), + }, } - }) - - 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 index 6f6e89020b..cac682c504 100644 --- a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js +++ b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js @@ -1,68 +1,24 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' import { setupI18n } from '@lingui/core' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +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 { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' import { createMutationSchema } from '../../../mutation' import { createQuerySchema } from '../../../query' import { cleanseInput } from '../../../validators' -import { loadOrgByKey } from '../../../organization/loaders' +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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('invite user to org', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - tokenize, - user + let query, drop, truncate, schema, collections, transaction, i18n, tokenize, user, org, userToInvite const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -97,6 +53,18 @@ describe('invite user to org', () => { 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({ @@ -111,79 +79,68 @@ describe('invite user to org', () => { 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', + 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('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', - }) + 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() + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() - const response = await graphql( - schema, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -198,89 +155,87 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, + }), + 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.', - }, + 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({ + 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', - preferredLang: 'english', - }) + ...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() + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() - const response = await graphql( - schema, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -295,89 +250,87 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, + }), + 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.', - }, + 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({ + 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', - preferredLang: 'english', - }) + ...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() + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() - const response = await graphql( - schema, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -392,82 +345,81 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, + }), + 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.', - }, + 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', - }) + 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, - ` + }) + 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)}" - preferredLang: ENGLISH } ) { result { @@ -482,88 +434,87 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, + }), + 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 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, - }) + 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() + }) + describe('requested role is admin', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() - const response = await graphql( - schema, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -578,89 +529,88 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, + }), + 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 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}` + 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, - }) + 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() + }) + describe('requested role is user', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() - const response = await graphql( - schema, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -675,108 +625,106 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, + }), + 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 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}` + 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, - }) + 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('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', - }) + }) + 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() + }) + it('returns status message', async () => { + const sendOrgInviteEmail = jest.fn() - const response = await graphql( - schema, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -791,89 +739,87 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, + }), + 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.', - }, + 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({ + 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', - preferredLang: 'english', - }) + ...secondaryUser, + }, + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', }) - it('returns status message', async () => { - const sendOrgInviteEmail = jest.fn() + }) + }) + 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, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -888,83 +834,82 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteEmail: sendOrgInviteEmail }, - validators: { cleanseInput }, + }), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, - ) - - const expectedResponse = { - data: { - inviteUserToOrg: { - result: { - status: - 'Successfully invited user to organization, and sent notification email.', - }, - }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), }, - } + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, + }, + }) - 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, + const expectedResponse = { + data: { + inviteUserToOrg: { + result: { + status: 'Successfully invited user to organization, and sent notification email.', + }, }, - 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, - ` + 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)}" - preferredLang: ENGLISH } ) { result { @@ -979,89 +924,88 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, + }), + 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 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}` + 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, - }) + 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() + }) + describe('requested role is user', () => { + it('returns status message', async () => { + const sendOrgInviteCreateAccount = jest.fn() - const response = await graphql( - schema, - ` + const response = await graphql({ + schema, + source: ` mutation { inviteUserToOrg( input: { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -1076,1712 +1020,336 @@ describe('invite user to org', () => { } } `, - null, - { - 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' }), + 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 }), - loadUserByUserName: loadUserByUserName({ query }), - }, - notify: { sendOrgInviteCreateAccount }, - validators: { cleanseInput }, + }), + 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 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}` + 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, - }) + 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 language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: { plurals: {} }, - fr: { plurals: {} }, + }) + 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', }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, + { returnNew: true }, + ) + ).new + userToInvite = ( + await collections.users.save( + { + userName: 'usertoinvite@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', }, - }) - }) - 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', + { 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', + }, }, }, - }) - }) - 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() + { returnNew: true }, + ) + ).new + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) - 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 - } - } - } + 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)}" } - `, - null, - { - i18n, - request: { - language: 'fr', - }, - 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: '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 - } - } + ) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description } } - `, - null, - { - i18n, - request: { - language: 'fr', - }, - 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: '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é.", - }, - }, - }, - } + } + } + `, + 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 }, + }, + }) - 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 error = { + data: { + inviteUserToOrg: { + result: { + code: 400, + description: 'Unable to invite yourself to an org.', + }, + }, + }, + } - 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: 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: '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: 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: '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: 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: '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: 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: '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: 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: '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: 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: '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: 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: '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: 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: '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: 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: { - 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: 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(), - }, - }, - 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: 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(), - }, - }, - 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: 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(), - }, - }, - 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 + 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 } } } } `, - null, - { - 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(), - }, - }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, + rootValue: null, + contextValue: { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, }, - ) - - const error = { - data: { - inviteUserToOrg: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with user invitations.', - }, - }, + 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(), }, - } - - 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: collectionNames, - 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(), - tfaRequired: 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 }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), }, - ) - - 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: collectionNames, - 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(), - tfaRequired: 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 }, + loadUserByKey: { + load: jest.fn(), }, - ) + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), + }, + }, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) - const error = [ - new GraphQLError('Unable to invite user. Please try again.'), - ] + const error = { + data: { + inviteUserToOrg: { + result: { + code: 400, + description: 'Unable to invite user to unknown organization.', + }, + }, + }, + } - 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`, - ]) - }) - }) + 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('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, - ` + 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.account@istio.actually.exists" + userName: "test@email.gc.ca" requestedRole: USER - orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH + orgId: "${toGlobalId('organizations', 123)}" } ) { result { @@ -2796,74 +1364,74 @@ describe('invite user to org', () => { } } `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, + 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 }), }, - 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(), + loadUserByKey: { + load: jest.fn(), }, - loaders: { - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, }, - ) + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) - const error = { - data: { - inviteUserToOrg: { - result: { - code: 400, - description: "Impossible de s'inviter à un org.", - }, + 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 themselves to 1.`, - ]) - }) + 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 attempts to invite to an org that does not exist', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` + }) + 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', 1)}" - preferredLang: FRENCH + orgId: "${toGlobalId('organizations', 123)}" } ) { result { @@ -2878,73 +1446,74 @@ describe('invite user to org', () => { } } `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, + 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 }), }, - 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(), + loadUserByKey: { + load: jest.fn(), }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, }, - ) + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, + }, + }) - const error = { - data: { - inviteUserToOrg: { - result: { - code: 400, - description: - "Impossible d'inviter un utilisateur à une organisation inconnue.", - }, + 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 1 however there is no org associated with that id.`, - ]) - }) + 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 undefined permission attempts to invite a user', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` + }) + 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: USER + requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH } ) { result { @@ -2959,73 +1528,75 @@ describe('invite user to org', () => { } } `, - null, - { - i18n, - request: { - language: 'fr', - protocol: 'https', - get: (text) => text, + 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 }), }, - 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(), + loadUserByKey: { + load: jest.fn(), }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, + loadUserByUserName: { + load: jest.fn(), + }, + loadOrganizationNamesById: { + load: jest.fn(), }, - notify: { sendOrgInviteCreateAccount: jest.fn() }, - validators: { cleanseInput }, }, - ) + 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.", - }, + 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: user but does not have permission to do so.`, - ]) - }) + 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('user with user level permission attempts to invite a user', () => { + }) + describe('transaction error occurs', () => { + describe('when creating affiliation', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { inviteUserToOrg( input: { - userName: "test@email.gc.ca" + userName: "${userToInvite.userName}" requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH + orgId: "${toGlobalId('organizations', org._key)}" } ) { result { @@ -3040,8 +1611,8 @@ describe('invite user to org', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request: { language: 'fr', @@ -3050,10 +1621,13 @@ describe('invite user to org', () => { }, query, collections: collectionNames, - transaction, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('trx step err'), + abort: jest.fn(), + }), userKey: 123, auth: { - checkPermission: jest.fn().mockReturnValue('user'), + checkPermission: jest.fn().mockReturnValue('admin'), tokenize, userRequired: jest.fn().mockReturnValue({ userName: 'test.account@exists.ca', @@ -3062,28 +1636,22 @@ describe('invite user to org', () => { tfaRequired: jest.fn(), }, loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, + 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: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", + code: 500, + description: 'Unable to invite user. Please try again.', }, }, }, @@ -3091,22 +1659,21 @@ describe('invite user to org', () => { 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.`, + `Transaction step error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: treasury-board-secretariat, error: trx step err`, ]) }) }) - describe('user with admin level permission attempts to invite a user to super_admin permission', () => { + describe('when committing transaction', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { inviteUserToOrg( input: { - userName: "test@email.gc.ca" - requestedRole: SUPER_ADMIN - orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH + userName: "${userToInvite.userName}" + requestedRole: USER + orgId: "${toGlobalId('organizations', org._key)}" } ) { result { @@ -3121,8 +1688,8 @@ describe('invite user to org', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request: { language: 'fr', @@ -3131,7 +1698,11 @@ describe('invite user to org', () => { }, query, collections: collectionNames, - transaction, + 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'), @@ -3143,28 +1714,25 @@ describe('invite user to org', () => { tfaRequired: jest.fn(), }, loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ _key: 123 }), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), + loadOrganizationNamesById: loadOrganizationNamesById({ query }), + }, + notify: { + sendOrgInviteCreateAccount: jest.fn(), + sendOrgInviteEmail: 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.", + code: 500, + description: 'Unable to invite user. Please try again.', }, }, }, @@ -3172,174 +1740,10 @@ describe('invite user to org', () => { 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.`, + `Transaction commit error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: treasury-board-secretariat, error: trx commit err`, ]) }) }) - 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: collectionNames, - 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(), - tfaRequired: 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: collectionNames, - 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(), - tfaRequired: 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/src/affiliation/mutations/__tests__/leave-organization.test.js b/api/src/affiliation/mutations/__tests__/leave-organization.test.js index 9d75a6a8f5..61400c9785 100644 --- a/api/src/affiliation/mutations/__tests__/leave-organization.test.js +++ b/api/src/affiliation/mutations/__tests__/leave-organization.test.js @@ -1,5 +1,6 @@ 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' @@ -12,54 +13,12 @@ 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('given a successful leave', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - user, - org, - domain, - domain2 + let query, drop, truncate, schema, collections, transaction, i18n, user, org, domain, domain2 const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -143,38 +102,27 @@ describe('given a successful leave', () => { _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({ + + 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, }) @@ -194,345 +142,10 @@ describe('given a successful leave', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) }) 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: 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 }, - }, - ) - - 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: 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 }, - }, - ) - - 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: 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 }, - }, - ) - - 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: 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 }, - }, - ) - - 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({ @@ -549,9 +162,9 @@ describe('given a successful leave', () => { }) }) it('returns status success message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -570,8 +183,8 @@ describe('given a successful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -604,23 +217,20 @@ describe('given a successful leave', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { leaveOrganization: { result: { - status: - 'Successfully left organization: treasury-board-secretariat', + status: 'Successfully left organization: treasury-board-secretariat', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) }) describe('users language is set to french', () => { @@ -639,9 +249,9 @@ describe('given a successful leave', () => { }) }) it('returns status success message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -660,8 +270,8 @@ describe('given a successful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -694,23 +304,20 @@ describe('given a successful leave', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { leaveOrganization: { result: { - status: - "L'organisation a été quittée avec succès: treasury-board-secretariat", + 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.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) }) }) @@ -746,37 +353,46 @@ describe('given a successful leave', () => { _to: domain._id, }) }) - describe('org is owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._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('removes all dmarc summary data', async () => { - await graphql( + it('returns status success message', async () => { + const response = await graphql({ schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" + source: ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } + ... on AffiliationError { + code + description } } } - `, - null, - { + } + `, + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -809,39 +425,41 @@ describe('given a successful leave', () => { }, 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 expectedResult = { + data: { + leaveOrganization: { + result: { + status: 'Successfully left organization: treasury-board-secretariat', + }, + }, + }, + } - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) }) - describe('org is not the owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org2._id, - _to: domain._id, + 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('does not remove all dmarc summary data', async () => { - await graphql( + it('returns status success message', async () => { + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -860,8 +478,8 @@ describe('given a successful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -894,1683 +512,120 @@ describe('given a successful leave', () => { }, 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: 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 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: 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 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: 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 }, - }, - ) - - 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: 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, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - 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('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: 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 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: 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 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: 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 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: 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 }, - }, - ) - - 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: 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('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: 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, - ` - 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 }), - }, + const expectedResult = { + data: { + leaveOrganization: { + result: { + status: "L'organisation a été quittée avec succès: treasury-board-secretariat", }, - 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.`, - ]) - }) + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) - 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.')), - }) + }) + }) + }) +}) +describe('given an unsuccessful leave', () => { + let schema, i18n - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } + 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 } } - `, - 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 }, + } + } + `, + 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 error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] + const expectedResult = { + data: { + leaveOrganization: { + result: { + code: 400, + description: 'Unable to leave undefined organization.', + }, + }, + }, + } - 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.`, - ]) - }) - }) + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User 123 attempted to leave undefined organization: 123`]) }) }) describe('user is not an org owner', () => { @@ -2582,14 +637,13 @@ describe('given an unsuccessful leave', () => { }) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), + step: jest.fn().mockRejectedValue(new Error('Step error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -2608,8 +662,8 @@ describe('given an unsuccessful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: jest.fn({ property: 'string' }), @@ -2630,11 +684,9 @@ describe('given an unsuccessful leave', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] + const error = [new GraphQLError('Unable leave organization. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2655,11 +707,12 @@ describe('given an unsuccessful leave', () => { 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( + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -2678,8 +731,8 @@ describe('given an unsuccessful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: jest.fn({ property: 'string' }), @@ -2700,11 +753,9 @@ describe('given an unsuccessful leave', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] + const error = [new GraphQLError('Unable leave organization. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2730,9 +781,9 @@ describe('given an unsuccessful leave', () => { }) describe('org cannot be found', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -2751,8 +802,8 @@ describe('given an unsuccessful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), collections: jest.fn(), @@ -2773,741 +824,21 @@ describe('given an unsuccessful leave', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { leaveOrganization: { result: { code: 400, - description: - 'Impossible de quitter une organisation non définie.', + 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.`, - ]) - }) - }) + expect(consoleOutput).toEqual([`User 123 attempted to leave undefined organization: 123`]) }) }) describe('user is not an org owner', () => { @@ -3519,14 +850,13 @@ describe('given an unsuccessful leave', () => { }) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), + step: jest.fn().mockRejectedValue(new Error('Step error occurred.')), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -3545,8 +875,8 @@ describe('given an unsuccessful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: jest.fn({ property: 'string' }), @@ -3567,13 +897,9 @@ describe('given an unsuccessful leave', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de quitter l'organisation. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3594,11 +920,12 @@ describe('given an unsuccessful leave', () => { 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( + const response = await graphql({ schema, - ` + source: ` mutation { leaveOrganization ( input: { @@ -3617,8 +944,8 @@ describe('given an unsuccessful leave', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: jest.fn({ property: 'string' }), @@ -3639,13 +966,9 @@ describe('given an unsuccessful leave', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de quitter l'organisation. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js b/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js index 139312fa8f..f994ded22e 100644 --- a/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js +++ b/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js @@ -1,5 +1,6 @@ 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' @@ -8,66 +9,17 @@ 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 { 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - const orgOneData = { verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'treasury-board-secretariat', @@ -94,18 +46,6 @@ const orgOneData = { const orgTwoData = { verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'communications-security-establishment', @@ -133,7 +73,6 @@ const orgTwoData = { const adminData = { userName: 'admin.account@istio.actually.exists', displayName: 'Test Admin', - preferredLang: 'french', tfaValidated: false, emailValidated: true, tfaSendMethod: 'email', @@ -142,25 +81,13 @@ const adminData = { 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) @@ -229,9 +156,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: { @@ -255,13 +182,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -295,7 +223,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const loader = loadAffiliationByKey({ query, @@ -305,10 +233,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(() => { @@ -326,9 +254,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: { @@ -352,13 +280,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -392,7 +321,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { @@ -408,10 +337,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', () => { @@ -430,9 +359,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: { @@ -456,13 +385,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -496,14 +426,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', @@ -513,18 +442,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.skip('removes the user from the org', async () => { - await graphql( + it('removes the user from the org', async () => { + await graphql({ schema, - ` + source: ` mutation { removeUserFromOrg ( input: { @@ -548,13 +477,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -588,7 +518,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const loader = loadAffiliationByKey({ query, @@ -598,10 +528,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(() => { @@ -619,9 +549,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: { @@ -645,13 +575,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -685,7 +616,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { @@ -701,10 +632,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', () => { @@ -723,9 +654,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: { @@ -749,13 +680,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -789,14 +721,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', @@ -806,10 +737,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) }) }) }) @@ -833,9 +764,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: { @@ -859,13 +790,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -899,7 +831,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const loader = loadAffiliationByKey({ query, @@ -909,10 +841,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(() => { @@ -930,9 +862,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: { @@ -956,13 +888,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -996,7 +929,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResponse = { data: { @@ -1012,10 +945,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', () => { @@ -1034,9 +967,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: { @@ -1060,13 +993,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: admin._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ i18n, @@ -1100,14 +1034,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', @@ -1117,10 +1050,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) }) }) }) @@ -1144,9 +1077,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: { @@ -1170,13 +1103,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(), userRequired: jest.fn().mockReturnValue({ @@ -1195,24 +1129,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', () => { @@ -1225,9 +1158,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: { @@ -1251,13 +1184,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn().mockReturnValue({ @@ -1278,23 +1212,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', () => { @@ -1307,9 +1241,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: { @@ -1333,13 +1267,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn().mockReturnValue({ @@ -1360,23 +1295,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', () => { @@ -1389,9 +1324,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: { @@ -1415,13 +1350,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -1440,24 +1376,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', () => { @@ -1467,9 +1402,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: { @@ -1493,13 +1428,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('super_admin'), userRequired: jest.fn().mockReturnValue({ @@ -1520,203 +1456,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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: 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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: 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: { @@ -1740,13 +1507,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -1767,18 +1535,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) }) }) }) @@ -1791,9 +1555,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: { @@ -1817,13 +1581,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -1844,18 +1609,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) }) }) }) @@ -1870,11 +1631,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: { @@ -1898,13 +1660,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -1925,18 +1688,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) }) }) }) @@ -1951,11 +1710,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: { @@ -1979,13 +1739,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -2006,18 +1767,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) }) }) }) @@ -2039,9 +1796,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: { @@ -2065,13 +1822,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(), userRequired: jest.fn().mockReturnValue({ @@ -2090,24 +1848,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', () => { @@ -2120,9 +1877,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: { @@ -2146,13 +1903,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('user'), userRequired: jest.fn().mockReturnValue({ @@ -2173,7 +1931,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { @@ -2181,16 +1939,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', () => { @@ -2203,9 +1961,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: { @@ -2229,13 +1987,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn().mockReturnValue({ @@ -2256,7 +2015,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { @@ -2264,16 +2023,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', () => { @@ -2286,9 +2045,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: { @@ -2312,13 +2071,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -2337,24 +2097,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', () => { @@ -2364,9 +2123,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: { @@ -2390,13 +2149,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('super_admin'), userRequired: jest.fn().mockReturnValue({ @@ -2417,7 +2177,7 @@ describe('given the removeUserFromOrg mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const error = { data: { @@ -2431,189 +2191,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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: 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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: 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: { @@ -2637,13 +2229,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -2664,18 +2257,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) }) }) }) @@ -2688,9 +2279,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: { @@ -2714,13 +2305,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -2741,18 +2333,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) }) }) }) @@ -2767,11 +2357,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: { @@ -2795,13 +2386,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -2822,18 +2414,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) }) }) }) @@ -2848,11 +2438,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: { @@ -2876,13 +2467,14 @@ describe('given the removeUserFromOrg mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -2903,18 +2495,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/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js b/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js index dbb726fb3e..86728b11e0 100644 --- a/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js +++ b/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js @@ -1,5 +1,6 @@ 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' @@ -12,53 +13,12 @@ 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - 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) @@ -140,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 () => { @@ -158,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: { @@ -180,8 +138,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -215,7 +173,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const testAffiliationCursor = await query` FOR aff IN affiliations @@ -223,12 +181,12 @@ describe('given the transferOrgOwnership mutation', () => { 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: { @@ -248,8 +206,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -283,7 +241,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const testAffiliationCursor = await query` FOR aff IN affiliations @@ -291,7 +249,7 @@ describe('given the transferOrgOwnership mutation', () => { 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(() => { @@ -307,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: { @@ -329,8 +287,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -368,7 +326,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -401,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: { @@ -423,8 +381,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -462,7 +420,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -501,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: { @@ -523,8 +481,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -553,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: collectionNames, - 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: { @@ -674,8 +552,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -703,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.', }, }, }, @@ -725,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: { @@ -747,8 +624,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -774,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.', }, }, }, @@ -796,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: { @@ -818,8 +694,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, @@ -847,7 +723,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -870,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: { @@ -892,8 +768,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('Database error')), collections: collectionNames, @@ -921,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([ @@ -941,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: { @@ -965,8 +838,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, @@ -994,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([ @@ -1011,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: { @@ -1039,8 +906,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, @@ -1068,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([ @@ -1088,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: { @@ -1112,8 +976,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, @@ -1141,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([ @@ -1173,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: { @@ -1195,8 +1055,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1225,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: collectionNames, - 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: { @@ -1346,8 +1126,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1375,7 +1155,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -1397,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: { @@ -1419,8 +1199,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1446,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.", }, }, }, @@ -1468,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: { @@ -1490,8 +1269,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, @@ -1519,7 +1298,7 @@ describe('given the transferOrgOwnership mutation', () => { }, validators: { cleanseInput }, }, - ) + }) const expectedResult = { data: { @@ -1542,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: { @@ -1564,8 +1343,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('Database error')), collections: collectionNames, @@ -1593,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) @@ -1613,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: { @@ -1637,8 +1415,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, @@ -1666,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) @@ -1683,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: { @@ -1711,8 +1485,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, @@ -1740,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) @@ -1760,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: { @@ -1784,8 +1557,8 @@ describe('given the transferOrgOwnership mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, @@ -1813,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 index ebd678e573..7d455c4e91 100644 --- a/api/src/affiliation/mutations/__tests__/update-user-role.test.js +++ b/api/src/affiliation/mutations/__tests__/update-user-role.test.js @@ -1,4 +1,5 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +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' @@ -8,51 +9,14 @@ 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 { 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('update a users role', () => { let query, drop, truncate, schema, collections, transaction, i18n, user @@ -61,6 +25,12 @@ describe('update a users role', () => { 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 @@ -148,7 +118,6 @@ describe('update a users role', () => { secondaryUser = await collections.users.save({ displayName: 'Test Account', userName: 'test@email.gc.ca', - preferredLang: 'english', }) }) describe('requesting user is a super admin', () => { @@ -169,9 +138,9 @@ describe('update a users role', () => { }) describe('to super admin', () => { it('returns status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -195,13 +164,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -214,16 +184,18 @@ describe('update a users role', () => { 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: { @@ -238,17 +210,17 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) 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( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -272,13 +244,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -291,16 +264,18 @@ describe('update a users role', () => { 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: { @@ -315,10 +290,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to user in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -332,9 +307,9 @@ describe('update a users role', () => { }) describe('to super admin', () => { it('returns status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -358,13 +333,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -377,16 +353,18 @@ describe('update a users role', () => { 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: { @@ -401,17 +379,17 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) 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( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -435,13 +413,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -454,16 +433,18 @@ describe('update a users role', () => { 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: { @@ -478,10 +459,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -504,9 +485,9 @@ describe('update a users role', () => { }) describe('to admin', () => { it('returns status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -530,13 +511,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -549,16 +531,18 @@ describe('update a users role', () => { 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: { @@ -569,1599 +553,92 @@ describe('update a users role', () => { 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: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - 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 }), - }, - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - 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 }), - }, - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - 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 }), - }, - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - 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 }), - }, - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ - userKey: user._key, - query, - }), - 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 }), - }, - 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: collectionNames, - transaction, - userKey: 123, - 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(), - }, - }, - 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: collectionNames, - transaction, - userKey: 123, - 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().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: collectionNames, - transaction, - userKey: 123, - 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().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: collectionNames, - transaction, - userKey: 123, - 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, - }), - }, - }, - 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: collectionNames, - transaction, - userKey: 123, - 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, - }), - }, - }, - 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: collectionNames, - transaction, - userKey: 123, - 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, - }), - }, - }, - 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: collectionNames, - 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(), - tfaRequired: 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: collectionNames, - 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(), - tfaRequired: 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: collectionNames, - transaction: jest.fn(), - userKey: 123, - 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, - }), - }, - }, - 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: collectionNames, - transaction: jest.fn(), - userKey: 123, - 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, - }), - }, - }, - 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: collectionNames, - transaction: jest.fn(), - userKey: 123, - 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, - }), - }, - }, - 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: collectionNames, - transaction: jest.fn(), - userKey: 123, - 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, - }), - }, - }, - 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: collectionNames, - 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(), - tfaRequired: 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 + }, + }, + } + + 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 } - ... on AffiliationError { - code - description + ) { + result { + ... on UpdateUserRoleResult { + status + user { + displayName + } + } + ... on AffiliationError { + code + description + } } } } - } - `, - null, - { - 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'), - }), - userKey: 123, - 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', + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkPermission: checkPermission({ + userKey: user._key, + query, }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, + 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, }, }, - 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`, - ]) + expect(sendRoleChangeEmail).toHaveBeenCalledWith({ + user: expect.any(Object), + newRole: 'user', + oldRole: expect.any(String), + orgNames, + }) + }) }) }) }) }) - describe('users language is set to french', () => { + }) + describe('given an unsuccessful update', () => { + describe('users language is set to english', () => { beforeAll(() => { i18n = setupI18n({ - locale: 'fr', + locale: 'en', localeData: { en: { plurals: {} }, fr: { plurals: {} }, @@ -2176,9 +653,9 @@ describe('update a users role', () => { describe('given an unsuccessful role update', () => { describe('user attempts to update their own role', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2199,13 +676,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn(), userRequired: jest.fn().mockReturnValue({ @@ -2222,35 +700,33 @@ describe('update a users role', () => { load: jest.fn(), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) const error = { data: { updateUserRole: { result: { code: 400, - description: - 'Impossible de mettre à jour votre propre rôle.', + 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) - 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( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2271,13 +747,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn(), userRequired: jest.fn().mockReturnValue({ @@ -2286,6 +763,7 @@ describe('update a users role', () => { verifiedRequired: jest.fn(), tfaRequired: jest.fn(), }, + notify: { sendRoleChangeEmail }, loaders: { loadOrgByKey: { load: jest.fn(), @@ -2298,31 +776,30 @@ describe('update a users role', () => { cleanseInput, }, }, - ) + }) const error = { data: { updateUserRole: { result: { code: 400, - description: - 'Impossible de mettre à jour le rôle : utilisateur inconnu.', + 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.`, ]) + 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( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2343,13 +820,14 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn(), userRequired: jest.fn().mockReturnValue({ @@ -2358,6 +836,7 @@ describe('update a users role', () => { verifiedRequired: jest.fn(), tfaRequired: jest.fn(), }, + notify: { sendRoleChangeEmail }, loaders: { loadOrgByKey: { load: jest.fn().mockReturnValue(undefined), @@ -2372,31 +851,30 @@ describe('update a users role', () => { cleanseInput, }, }, - ) + }) const error = { data: { updateUserRole: { result: { code: 400, - description: - 'Impossible de mettre à jour le rôle : organisation inconnue.', + 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.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user permission is user', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2417,13 +895,14 @@ describe('update a users role', () => { } } `, - null, - { + 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({ @@ -2444,35 +923,36 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) const error = { data: { updateUserRole: { result: { - code: 400, + code: 403, description: - "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 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.`, ]) + 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( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2493,13 +973,14 @@ describe('update a users role', () => { } } `, - null, - { + 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({ @@ -2520,35 +1001,36 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) const error = { data: { updateUserRole: { result: { - code: 400, + code: 403, description: - "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 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.`, ]) + 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( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2569,13 +1051,14 @@ describe('update a users role', () => { } } `, - null, - { + 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({ @@ -2596,204 +1079,36 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) const error = { data: { updateUserRole: { result: { code: 400, - description: - "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.", + 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: collectionNames, - 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(), - tfaRequired: 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: collectionNames, - 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(), - tfaRequired: 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.`, - ]) - }) - }) + 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( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2814,18 +1129,17 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1, - next: jest - .fn() - .mockReturnValue({ permission: 'super_admin' }), + 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({ @@ -2846,107 +1160,28 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, 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.", + code: 403, + description: 'Permission Denied: Please contact super admin for help with user role changes.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, + `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.`, ]) - }) - }) - 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: collectionNames, - transaction: jest.fn(), - userKey: 123, - 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, - }), - }, - }, - 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.`, - ]) }) }) }) @@ -2954,9 +1189,9 @@ describe('update a users role', () => { describe('database error occurs', () => { describe('when getting current affiliation', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -2977,13 +1212,14 @@ describe('update a users role', () => { } } `, - null, - { + 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({ @@ -3004,31 +1240,28 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] + 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`, ]) + expect(response.errors).toEqual(error) }) }) }) describe('cursor error occur', () => { describe('when gathering affiliation info', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -3049,8 +1282,8 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1, @@ -3059,6 +1292,7 @@ describe('update a users role', () => { collections: collectionNames, transaction: jest.fn(), userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn().mockReturnValue({ @@ -3079,31 +1313,28 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] + 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`, ]) + expect(response.errors).toEqual(error) }) }) }) describe('transaction error occurs', () => { describe('when running transaction', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -3124,8 +1355,8 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1, @@ -3134,8 +1365,10 @@ describe('update a users role', () => { 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({ @@ -3156,29 +1389,26 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] + 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`, ]) + expect(response.errors).toEqual(error) }) }) describe('when committing transaction', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserRole ( input: { @@ -3199,8 +1429,8 @@ describe('update a users role', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockReturnValue({ count: 1, @@ -3210,8 +1440,10 @@ describe('update a users role', () => { 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({ @@ -3232,22 +1464,19 @@ describe('update a users role', () => { }), }, }, + notify: { sendRoleChangeEmail }, validators: { cleanseInput, }, }, - ) + }) - const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), - ] + 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`, ]) + expect(response.errors).toEqual(error) }) }) }) diff --git a/api/src/affiliation/mutations/index.js b/api/src/affiliation/mutations/index.js index 3b3584b898..557b4f8353 100644 --- a/api/src/affiliation/mutations/index.js +++ b/api/src/affiliation/mutations/index.js @@ -1,5 +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 index 3d3ec9c6e4..8398c497aa 100644 --- a/api/src/affiliation/mutations/invite-user-to-org.js +++ b/api/src/affiliation/mutations/invite-user-to-org.js @@ -4,7 +4,9 @@ import { GraphQLEmailAddress } from 'graphql-scalars' import { t } from '@lingui/macro' import { inviteUserToOrgUnion } from '../unions' -import { LanguageEnums, RoleEnums } from '../../enums' +import { logActivity } from '../../audit-logs/mutations/log-activity' +import { InvitationRoleEnums } from '../../enums' +import ac from '../../access-control' export const inviteUserToOrg = new mutationWithClientMutationId({ name: 'InviteUserToOrg', @@ -13,21 +15,17 @@ 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), + type: new GraphQLNonNull(GraphQLEmailAddress), description: 'Users email that you would like to invite to your org.', }, requestedRole: { - type: GraphQLNonNull(RoleEnums), + type: new GraphQLNonNull(InvitationRoleEnums), description: 'The role which you would like this user to have.', }, orgId: { - type: GraphQLNonNull(GraphQLID), + type: new 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: { @@ -46,14 +44,9 @@ able to sign-up and be assigned to that organization in one mutation.`, collections, transaction, userKey, - auth: { - checkPermission, - tokenize, - userRequired, - verifiedRequired, - tfaRequired, - }, - loaders: { loadOrgByKey, loadUserByUserName }, + request: { ip }, + auth: { checkPermission, tokenize, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadOrgByKey, loadUserByUserName, loadOrganizationNamesById }, notify: { sendOrgInviteCreateAccount, sendOrgInviteEmail }, validators: { cleanseInput }, }, @@ -61,7 +54,6 @@ able to sign-up and be assigned to that organization in one mutation.`, 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() @@ -71,9 +63,7 @@ able to sign-up and be assigned to that organization in one mutation.`, // Make sure user is not inviting themselves if (user.userName === userName) { - console.warn( - `User: ${userKey} attempted to invite themselves to ${orgId}.`, - ) + console.warn(`User: ${userKey} attempted to invite themselves to ${orgId}.`) return { _type: 'error', code: 400, @@ -98,104 +88,198 @@ able to sign-up and be assigned to that organization in one mutation.`, // 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') - ) { + // 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 organization admin for help with user invitations.`, - ), + 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 user name send invite to org with create account + // 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 }, - expPeriod: 24, }) - const createAccountLink = `https://${request.get( - 'host', - )}/create-user/${token}` + const createAccountLink = `https://${request.get('host')}/create-user/${token}` await sendOrgInviteCreateAccount({ - user: { userName: userName, preferredLang }, - orgName: org.name, + 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}.`, - ) + 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.`, - ), + 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.`), } } - // If account is found add just add affiliation - else { - // Setup Transaction - const trx = await transaction(collections) - - // Create affiliation - try { - await trx.step( - () => - query` + + // 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}, - 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.`)) + ) + } 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, - 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.`)) - } + await sendOrgInviteEmail({ + user: requestedUser, + orgNameEN: orgNames.orgNameEN, + orgNameFR: orgNames.orgNameFR, + }) - console.info( - `User: ${userKey} successfully invited user: ${requestedUser._key} to the org: ${org.slug}.`, + // 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: 'regular', - status: i18n._( - t`Successfully invited user to organization, and sent notification email.`, - ), + _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 index 14d8c0903b..ce8fc78066 100644 --- a/api/src/affiliation/mutations/leave-organization.js +++ b/api/src/affiliation/mutations/leave-organization.js @@ -9,15 +9,14 @@ export const leaveOrganization = new mutationWithClientMutationId({ description: 'This mutation allows users to leave a given organization.', inputFields: () => ({ orgId: { - type: GraphQLNonNull(GraphQLID), + 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`.', + description: '`LeaveOrganizationUnion` resolving to either a `LeaveOrganizationResult` or `AffiliationError`.', resolve: (payload) => payload, }, }), @@ -28,7 +27,7 @@ export const leaveOrganization = new mutationWithClientMutationId({ query, collections, transaction, - auth: { checkOrgOwner, userRequired, verifiedRequired }, + auth: { userRequired, verifiedRequired }, loaders: { loadOrgByKey }, validators: { cleanseInput }, }, @@ -42,9 +41,7 @@ export const leaveOrganization = new mutationWithClientMutationId({ const org = await loadOrgByKey.load(orgKey) if (typeof org === 'undefined') { - console.warn( - `User ${user._key} attempted to leave undefined organization: ${orgKey}`, - ) + console.warn(`User ${user._key} attempted to leave undefined organization: ${orgKey}`) return { _type: 'error', code: 400, @@ -52,382 +49,33 @@ export const leaveOrganization = new mutationWithClientMutationId({ } } - // check to see if org owner - const owner = await checkOrgOwner({ orgId: org._id }) - // Setup Trans action const trx = await transaction(collections) - 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` + 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.`)) - } + ) + } 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}`, - ) + 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.`)) } diff --git a/api/src/affiliation/mutations/remove-user-from-org.js b/api/src/affiliation/mutations/remove-user-from-org.js index d9f02b39b6..88a780090b 100644 --- a/api/src/affiliation/mutations/remove-user-from-org.js +++ b/api/src/affiliation/mutations/remove-user-from-org.js @@ -3,18 +3,19 @@ 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.', + description: 'This mutation allows admins or higher to remove users from any organizations they belong to.', inputFields: () => ({ userId: { - type: GraphQLNonNull(GraphQLID), + type: new GraphQLNonNull(GraphQLID), description: 'The user id of the user to be removed.', }, orgId: { - type: GraphQLNonNull(GraphQLID), + type: new GraphQLNonNull(GraphQLID), description: 'The organization that the user is to be removed from.', }, }), @@ -34,6 +35,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ collections, transaction, userKey, + request: { ip }, auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, loaders: { loadOrgByKey, loadUserByKey }, validators: { cleanseInput }, @@ -58,24 +60,12 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to remove user from unknown organization.`, - ), + 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) @@ -86,9 +76,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to remove unknown user from organization.`, - ), + description: i18n._(t`Unable to remove unknown user from organization.`), } } @@ -105,11 +93,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ 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.`, - ), - ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) } if (affiliationCursor.count < 1) { @@ -119,9 +103,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to remove a user that already does not belong to this organization.`, - ), + description: i18n._(t`Unable to remove a user that already does not belong to this organization.`), } } @@ -132,31 +114,39 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ 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.`, - ), + 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.`), + } } - 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 + // 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.`), + } } - if (canRemove) { - // Setup Transaction - const trx = await transaction(collections) + // Setup Transaction + const trx = await transaction(collections) - try { - await trx.step(async () => { + try { + await trx.step( + () => query` WITH affiliations, organizations, users FOR aff IN affiliations @@ -164,55 +154,55 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ 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}.`, + `, + ) + } 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.`)) + } - 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.`, + 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}`, ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with removing users.`, - ), - } + 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 index 0037be9604..3f990982bc 100644 --- a/api/src/affiliation/mutations/transfer-org-ownership.js +++ b/api/src/affiliation/mutations/transfer-org-ownership.js @@ -6,18 +6,15 @@ 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.', + 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.', + type: new 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.', + type: new GraphQLNonNull(GraphQLID), + description: 'Id of the user that the org ownership is being transferred to.', }, }), outputFields: () => ({ @@ -55,28 +52,11 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ // ensure requested org is not undefined if (typeof org === 'undefined') { - console.warn( - `User: ${requestingUser._key} attempted to transfer org ownership of an undefined org.`, - ) + 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.`, - ), + description: i18n._(t`Unable to transfer ownership of undefined organization.`), } } @@ -91,9 +71,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Permission Denied: Please contact org owner to transfer ownership.`, - ), + description: i18n._(t`Permission Denied: Please contact org owner to transfer ownership.`), } } @@ -108,9 +86,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to transfer ownership of an org to an undefined user.`, - ), + description: i18n._(t`Unable to transfer ownership of an org to an undefined user.`), } } @@ -127,9 +103,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ 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.`), - ) + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } // check to see if requested user belongs to org @@ -159,7 +133,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ FILTER aff._from == ${org._id} FILTER aff._to == ${requestingUser._id} UPDATE { _key: aff._key } WITH { - owner: false, + permission: "admin", } IN affiliations RETURN aff `, @@ -168,9 +142,8 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ 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.`), - ) + await trx.abort() + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } // set new org owner @@ -183,7 +156,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ FILTER aff._from == ${org._id} FILTER aff._to == ${requestedUser._id} UPDATE { _key: aff._key } WITH { - owner: true, + permission: "owner", } IN affiliations RETURN aff `, @@ -192,9 +165,8 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ 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.`), - ) + await trx.abort() + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } // commit changes to the db @@ -204,9 +176,8 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ 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.`), - ) + await trx.abort() + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } console.info( @@ -214,9 +185,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ ) return { _type: 'regular', - status: i18n._( - t`Successfully transferred org: ${org.slug} ownership to user: ${requestedUser.userName}`, - ), + 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 index 5f70c4b5dd..1f92a58a5b 100644 --- a/api/src/affiliation/mutations/update-user-role.js +++ b/api/src/affiliation/mutations/update-user-role.js @@ -5,6 +5,8 @@ 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', @@ -13,25 +15,22 @@ update the permission level of a given user that already belongs to the given organization.`, inputFields: () => ({ userName: { - type: GraphQLNonNull(GraphQLEmailAddress), + type: new 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.', + type: new 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.', + 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.', + description: '`UpdateUserRoleUnion` returning either a `UpdateUserRoleResult`, or `UpdateUserRoleError` object.', resolve: (payload) => payload, }, }), @@ -43,9 +42,11 @@ given organization.`, collections, transaction, userKey, + request: { ip }, auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, - loaders: { loadOrgByKey, loadUserByUserName }, + loaders: { loadOrgByKey, loadUserByUserName, loadOrganizationNamesById }, validators: { cleanseInput }, + notify: { sendRoleChangeEmail }, }, ) => { // Cleanse Input @@ -61,9 +62,7 @@ given organization.`, // 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}.`, - ) + console.warn(`User: ${userKey} attempted to update their own role in org: ${orgId}.`) return { _type: 'error', code: 400, @@ -102,16 +101,15 @@ given organization.`, // Check requesting user's permission const permission = await checkPermission({ orgId: org._id }) - if (permission === 'user' || typeof permission === 'undefined') { + // 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: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with user role changes.`, - ), + code: 403, + description: i18n._(t`Permission Denied: Please contact organization admin for help with user role changes.`), } } @@ -128,9 +126,8 @@ given organization.`, 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.`), - ) + + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } if (affiliationCursor.count < 1) { @@ -140,9 +137,7 @@ given organization.`, return { _type: 'error', code: 400, - description: i18n._( - t`Unable to update role: user does not belong to organization.`, - ), + description: i18n._(t`Unable to update role: user does not belong to organization.`), } } @@ -153,81 +148,36 @@ given organization.`, 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.`), - ) - } - // Setup Transaction - const trx = await transaction(collections) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) + } - // 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') + // 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 ) { - // 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}.`, + `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: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating user roles.`, - ), + 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` @@ -242,9 +192,8 @@ given organization.`, 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.`), - ) + await trx.abort() + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } try { @@ -253,14 +202,51 @@ given organization.`, 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.`), + 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.`)) } - console.info( - `User: ${userKey} successful updated user: ${requestedUser._key} role to ${role} in org: ${org.slug}.`, - ) + 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', diff --git a/api/src/affiliation/objects/__tests__/affiliation-connection.test.js b/api/src/affiliation/objects/__tests__/affiliation-connection.test.js index d5d74fe064..313f98ebb6 100644 --- a/api/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/src/affiliation/objects/__tests__/affiliation-error.test.js b/api/src/affiliation/objects/__tests__/affiliation-error.test.js index 0ece85fcbd..d37dbde3d1 100644 --- a/api/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/src/affiliation/objects/__tests__/affiliation.test.js b/api/src/affiliation/objects/__tests__/affiliation.test.js index 4a09f67661..ea7daa0d19 100644 --- a/api/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/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js b/api/src/affiliation/objects/__tests__/invite-user-to-org-result.test.js index 2d470db76f..bbed55cfd1 100644 --- a/api/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/src/affiliation/objects/__tests__/leave-organization-result.test.js b/api/src/affiliation/objects/__tests__/leave-organization-result.test.js index d90a0b3770..7cfe917a3f 100644 --- a/api/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 index 66f7d839dd..11d972beb6 100644 --- 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 @@ -1,7 +1,7 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { removeUserFromOrgResultType } from '../remove-user-from-org-result' -import { userSharedType } from '../../../user/objects' +import {removeUserFromOrgResultType} from '../remove-user-from-org-result' +import {userSharedType} from '../../../user/objects' describe('given the removeUserFromOrgResultType object', () => { describe('testing the field definitions', () => { @@ -23,7 +23,7 @@ describe('given the removeUserFromOrgResultType object', () => { it('returns the resolved field', () => { const demoType = removeUserFromOrgResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + expect(demoType.status.resolve({status: 'status'})).toEqual('status') }) }) describe('testing the user resolver', () => { @@ -31,8 +31,8 @@ describe('given the removeUserFromOrgResultType object', () => { const demoType = removeUserFromOrgResultType.getFields() expect( - demoType.user.resolve({ user: { id: 1, userName: 'test@email.ca' } }), - ).toEqual({ id: 1, userName: 'test@email.ca' }) + 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 index 2111ad4bb6..6398cbbfe8 100644 --- a/api/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js +++ b/api/src/affiliation/objects/__tests__/transfer-org-ownership-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { transferOrgOwnershipResult } from '../transfer-org-ownership-result' +import {transferOrgOwnershipResult} from '../transfer-org-ownership-result' describe('given the transferOrgOwnershipResult object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the transferOrgOwnershipResult object', () => { it('returns the resolved field', () => { const demoType = transferOrgOwnershipResult.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__/update-user-role-result.test.js b/api/src/affiliation/objects/__tests__/update-user-role-result.test.js index 75f3822744..e4482a6f64 100644 --- a/api/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 index 8f3b292810..125614258c 100644 --- a/api/src/affiliation/objects/affiliation-connection.js +++ b/api/src/affiliation/objects/affiliation-connection.js @@ -1,6 +1,6 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' -import { affiliationType } from './affiliation' +import {GraphQLInt} from 'graphql' +import {connectionDefinitions} from 'graphql-relay' +import {affiliationType} from './affiliation' export const affiliationConnection = connectionDefinitions({ name: 'Affiliation', @@ -9,7 +9,7 @@ export const affiliationConnection = connectionDefinitions({ totalCount: { type: GraphQLInt, description: 'The total amount of affiliations the user has access to.', - resolve: ({ totalCount }) => totalCount, + resolve: ({totalCount}) => totalCount, }, }), }) diff --git a/api/src/affiliation/objects/affiliation-error.js b/api/src/affiliation/objects/affiliation-error.js index 7ce345a5c1..118b33499d 100644 --- a/api/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 index 9490dd2181..e1b0f34f6c 100644 --- a/api/src/affiliation/objects/affiliation.js +++ b/api/src/affiliation/objects/affiliation.js @@ -1,10 +1,10 @@ -import { GraphQLObjectType } from 'graphql' -import { globalIdField } from 'graphql-relay' +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' +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', @@ -13,12 +13,12 @@ export const affiliationType = new GraphQLObjectType({ permission: { type: RoleEnums, description: "User's level of access to a given organization.", - resolve: ({ permission }) => permission, + resolve: ({permission}) => permission, }, user: { type: userSharedType, description: 'The affiliated users information.', - resolve: async ({ _to }, _args, { loaders: { loadUserByKey } }) => { + resolve: async ({_to}, _args, {loaders: {loadUserByKey}}) => { const userKey = _to.split('/')[1] const user = await loadUserByKey.load(userKey) user.id = user._key @@ -28,7 +28,7 @@ export const affiliationType = new GraphQLObjectType({ organization: { type: organizationType, description: 'The affiliated organizations information.', - resolve: async ({ _from }, _args, { loaders: { loadOrgByKey } }) => { + resolve: async ({_from}, _args, {loaders: {loadOrgByKey}}) => { const orgKey = _from.split('/')[1] const org = await loadOrgByKey.load(orgKey) org.id = org._key diff --git a/api/src/affiliation/objects/invite-user-to-org-result.js b/api/src/affiliation/objects/invite-user-to-org-result.js index 4c28896b5c..558ee56492 100644 --- a/api/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/src/affiliation/objects/leave-organization-result.js b/api/src/affiliation/objects/leave-organization-result.js index e125ae2ac7..0bd5bc7006 100644 --- a/api/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 index 5934e25e3e..c3f354dda0 100644 --- a/api/src/affiliation/objects/remove-user-from-org-result.js +++ b/api/src/affiliation/objects/remove-user-from-org-result.js @@ -1,6 +1,6 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' -import { userSharedType } from '../../user/objects' +import {userSharedType} from '../../user/objects' export const removeUserFromOrgResultType = new GraphQLObjectType({ name: 'RemoveUserFromOrgResult', @@ -9,12 +9,12 @@ export const removeUserFromOrgResultType = new GraphQLObjectType({ status: { type: GraphQLString, description: 'Informs the user if the user was successfully removed.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, user: { type: userSharedType, description: 'The user that was just removed.', - resolve: ({ user }) => user, + resolve: ({user}) => user, }, }), }) diff --git a/api/src/affiliation/objects/transfer-org-ownership-result.js b/api/src/affiliation/objects/transfer-org-ownership-result.js index 6df1ef5d74..126525b9cd 100644 --- a/api/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 index bbb29f7c01..dae75748dc 100644 --- a/api/src/affiliation/objects/update-user-role-result.js +++ b/api/src/affiliation/objects/update-user-role-result.js @@ -1,5 +1,5 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userSharedType } from '../../user/objects' +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userSharedType} from '../../user/objects' export const updateUserRoleResultType = new GraphQLObjectType({ name: 'UpdateUserRoleResult', @@ -10,12 +10,12 @@ export const updateUserRoleResultType = new GraphQLObjectType({ type: GraphQLString, description: "Informs the user if the user who's role was successfully updated.", - resolve: ({ status }) => status, + resolve: ({status}) => status, }, user: { type: userSharedType, description: "The user who's role was successfully updated.", - resolve: ({ user }) => user, + resolve: ({user}) => user, }, }), }) diff --git a/api/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js b/api/src/affiliation/unions/__tests__/invite-user-to-org-union.test.js index 86d8d0dff0..bfb8836b8e 100644 --- a/api/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/src/affiliation/unions/__tests__/leave-organization-union.test.js b/api/src/affiliation/unions/__tests__/leave-organization-union.test.js index 1167012309..d4d47a28a3 100644 --- a/api/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/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js b/api/src/affiliation/unions/__tests__/remove-user-from-org-union.test.js index dbba78f6ce..86ca9c1187 100644 --- a/api/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/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js b/api/src/affiliation/unions/__tests__/transfer-org-ownership-union.test.js index ca9f3603d0..a6a7616195 100644 --- a/api/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/src/affiliation/unions/__tests__/update-user-role-union.test.js b/api/src/affiliation/unions/__tests__/update-user-role-union.test.js index 7fcee8d466..035d76a207 100644 --- a/api/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/src/affiliation/unions/invite-user-to-org-union.js b/api/src/affiliation/unions/invite-user-to-org-union.js index ed17c8be65..ec5a8e5836 100644 --- a/api/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/src/affiliation/unions/leave-organization-union.js b/api/src/affiliation/unions/leave-organization-union.js index 79a27e0249..91b3c04872 100644 --- a/api/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/src/affiliation/unions/remove-user-from-org-union.js b/api/src/affiliation/unions/remove-user-from-org-union.js index da7486adc5..a389a8aa95 100644 --- a/api/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/src/affiliation/unions/transfer-org-ownership-union.js b/api/src/affiliation/unions/transfer-org-ownership-union.js index 65c03043ab..bf9bf28094 100644 --- a/api/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/src/affiliation/unions/update-user-role-union.js b/api/src/affiliation/unions/update-user-role-union.js index 9356e5d724..9c54aa83cf 100644 --- a/api/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/__tests__/check-domain-ownership.test.js b/api/src/auth/__tests__/check-domain-ownership.test.js deleted file mode 100644 index f7d5a9b06f..0000000000 --- a/api/src/auth/__tests__/check-domain-ownership.test.js +++ /dev/null @@ -1,507 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -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, 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({ - 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({ - 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, - 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({ - 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 - 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', - auth: { loginRequiredBool: true }, - }) - 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/src/auth/__tests__/verify-jwt.test.js b/api/src/auth/__tests__/verify-jwt.test.js deleted file mode 100644 index 33f2795cb5..0000000000 --- a/api/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/src/auth/check-domain-ownership.js b/api/src/auth/check-domain-ownership.js deleted file mode 100644 index a95aead2bf..0000000000 --- a/api/src/auth/check-domain-ownership.js +++ /dev/null @@ -1,83 +0,0 @@ -import { t } from '@lingui/macro' - -export const checkDomainOwnership = ({ - i18n, - query, - userKey, - auth: { loginRequiredBool }, -}) => 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 || !loginRequiredBool) { - 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 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/src/auth/check-domain-permission.js b/api/src/auth/check-domain-permission.js deleted file mode 100644 index 001df9347f..0000000000 --- a/api/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/src/auth/check-org-owner.js b/api/src/auth/check-org-owner.js deleted file mode 100644 index 647a1de240..0000000000 --- a/api/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/src/auth/check-permission.js b/api/src/auth/check-permission.js deleted file mode 100644 index cdba58f1f1..0000000000 --- a/api/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/src/auth/check-super-admin.js b/api/src/auth/check-super-admin.js deleted file mode 100644 index e97c6a027f..0000000000 --- a/api/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/src/auth/check-user-belongs-to-org.js b/api/src/auth/check-user-belongs-to-org.js deleted file mode 100644 index 6909048a8c..0000000000 --- a/api/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/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/src/auth/__tests__/check-domain-permission.test.js b/api/src/auth/checks/__tests__/check-domain-permission.test.js similarity index 87% rename from api/src/auth/__tests__/check-domain-permission.test.js rename to api/src/auth/checks/__tests__/check-domain-permission.test.js index 4afa1f4b22..0682857b02 100644 --- a/api/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 { checkDomainPermission } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import dbschema from '../../../database.json' +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,22 +25,21 @@ describe('given the check domain permission function', () => { let user, permitted beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -153,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' }) @@ -178,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, @@ -189,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.`, ]) @@ -215,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.`, ]) @@ -234,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, @@ -246,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.`, ]) @@ -276,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, @@ -332,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/src/auth/__tests__/check-org-owner.test.js b/api/src/auth/checks/__tests__/check-org-owner.test.js similarity index 78% rename from api/src/auth/__tests__/check-org-owner.test.js rename to api/src/auth/checks/__tests__/check-org-owner.test.js index c9d2fef879..e2a97b2e87 100644 --- a/api/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 { checkOrgOwner } from '../check-org-owner' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import dbschema from '../../../database.json' +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,22 +14,21 @@ describe('given the checkOrgOwner function', () => { let query, drop, truncate, collections, user, org beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) + 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, }) @@ -68,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 () => { @@ -86,7 +85,6 @@ describe('given the checkOrgOwner function', () => { _from: org._id, _to: user._id, permission: 'admin', - owner: false, }) }) it('returns false', async () => { @@ -123,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, @@ -136,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`, @@ -148,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({ @@ -162,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`, @@ -187,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, @@ -201,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([ @@ -214,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({ @@ -229,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/src/auth/__tests__/check-permission.test.js b/api/src/auth/checks/__tests__/check-permission.test.js similarity index 87% rename from api/src/auth/__tests__/check-permission.test.js rename to api/src/auth/checks/__tests__/check-permission.test.js index 434fab5db2..73171b4092 100644 --- a/api/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 { checkPermission } from '..' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import dbschema from '../../../database.json' +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,22 +24,21 @@ describe('given the check permission function', () => { describe('given a successful permission call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -194,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) }) }) }) @@ -217,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({ @@ -229,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([ @@ -258,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([ @@ -287,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([ @@ -321,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([ @@ -351,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({ @@ -363,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([ @@ -394,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([ @@ -425,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([ @@ -461,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/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/src/auth/__tests__/check-super-admin.test.js rename to api/src/auth/checks/__tests__/check-super-admin.test.js index 2720658e10..f355b27bce 100644 --- a/api/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 { checkSuperAdmin } from '../check-super-admin' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import dbschema from '../../../database.json' +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,22 +24,21 @@ describe('given the check super admin function', () => { describe('given a successful call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -167,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({ @@ -179,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([ @@ -206,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([ @@ -234,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({ @@ -246,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([ @@ -275,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/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/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 ce84bc53ad..6956696979 100644 --- a/api/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 { checkUserBelongsToOrg } from '../check-user-belongs-to-org' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import dbschema from '../../../database.json' +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,22 +14,21 @@ describe('given the checkUserBelongsToOrg function', () => { let query, drop, truncate, collections, user, org beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -68,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 () => { @@ -121,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, @@ -134,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`, @@ -161,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, @@ -174,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/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/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 b720fce154..2a14fe5334 100644 --- a/api/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 { checkUserIsAdminForUser } from '../index' -import englishMessages from '../../locale/en/messages' -import frenchMessages from '../../locale/fr/messages' -import dbschema from '../../../database.json' +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,29 +36,27 @@ describe('given the checkUserIsAdminForUser', () => { describe('given a successful call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + 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, }) @@ -229,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 { @@ -239,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.`, @@ -268,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.`, @@ -298,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.`, @@ -333,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.`, @@ -365,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 { @@ -375,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.`, @@ -406,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.`, @@ -438,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.`, @@ -475,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/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/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/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/generate-jwt.js b/api/src/auth/generate-jwt.js deleted file mode 100644 index 9aa7997311..0000000000 --- a/api/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/src/auth/__tests__/tfa-required.test.js b/api/src/auth/guards/__tests__/tfa-required.test.js similarity index 89% rename from api/src/auth/__tests__/tfa-required.test.js rename to api/src/auth/guards/__tests__/tfa-required.test.js index 69838b480f..c48657cef0 100644 --- a/api/src/auth/__tests__/tfa-required.test.js +++ b/api/src/auth/guards/__tests__/tfa-required.test.js @@ -1,8 +1,8 @@ import { setupI18n } from '@lingui/core' import { tfaRequired } 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 tfaRequired function', () => { let i18n, user @@ -23,7 +23,6 @@ describe('given the tfaRequired function', () => { user = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: true, tfaSendMethod: 'email', @@ -57,7 +56,6 @@ describe('given the tfaRequired function', () => { user = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, tfaSendMethod: 'none', @@ -70,9 +68,7 @@ describe('given the tfaRequired function', () => { tfaFunc({ user }) } catch (err) { expect(err).toEqual( - new Error( - 'Verification error. Please activate multi-factor authentication to access content.', - ), + new Error('Verification error. Please activate multi-factor authentication to access content.'), ) } }) @@ -97,7 +93,6 @@ describe('given the tfaRequired function', () => { user = { userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, tfaSendMethod: 'none', diff --git a/api/src/auth/__tests__/user-required.test.js b/api/src/auth/guards/__tests__/user-required.test.js similarity index 81% rename from api/src/auth/__tests__/user-required.test.js rename to api/src/auth/guards/__tests__/user-required.test.js index cb202ea50f..19c4a72715 100644 --- a/api/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 { 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 dbschema from '../../../database.json' +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,22 +27,21 @@ describe('given a loadUserByKey dataloader', () => { describe('given a successful call', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -95,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', () => { @@ -117,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([ @@ -153,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', () => { @@ -175,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([ @@ -221,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([ @@ -265,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/src/auth/__tests__/verified-required.test.js b/api/src/auth/guards/__tests__/verified-required.test.js similarity index 83% rename from api/src/auth/__tests__/verified-required.test.js rename to api/src/auth/guards/__tests__/verified-required.test.js index 3c0d6b0a20..4ea31d45de 100644 --- a/api/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 index 132de996b5..2f77a785a2 100644 --- a/api/src/auth/index.js +++ b/api/src/auth/index.js @@ -1,13 +1,4 @@ -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 './salted-hash' -export * from './user-required' -export * from './verified-required' -export * from './verify-jwt' -export * from './tfa-required' +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/src/auth/tfa-required.js b/api/src/auth/tfa-required.js deleted file mode 100644 index 56a60f8150..0000000000 --- a/api/src/auth/tfa-required.js +++ /dev/null @@ -1,18 +0,0 @@ -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/user-required.js b/api/src/auth/user-required.js deleted file mode 100644 index 3b46897f85..0000000000 --- a/api/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/src/auth/__tests__/generate-jwt.test.js b/api/src/auth/utils/__tests__/generate-jwt.test.js similarity index 94% rename from api/src/auth/__tests__/generate-jwt.test.js rename to api/src/auth/utils/__tests__/generate-jwt.test.js index d37cbe83bb..33fc30e388 100644 --- a/api/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/__tests__/salted-hash.test.js b/api/src/auth/utils/__tests__/salted-hash.test.js similarity index 87% rename from api/src/auth/__tests__/salted-hash.test.js rename to api/src/auth/utils/__tests__/salted-hash.test.js index c332e37478..66808d4e3e 100644 --- a/api/src/auth/__tests__/salted-hash.test.js +++ b/api/src/auth/utils/__tests__/salted-hash.test.js @@ -1,4 +1,4 @@ -import { saltedHash } from '../salted-hash' +import {saltedHash} from '../salted-hash' describe('saltedHash()', () => { describe('when passed data and a salt', () => { 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/salted-hash.js b/api/src/auth/utils/salted-hash.js similarity index 100% rename from api/src/auth/salted-hash.js rename to api/src/auth/utils/salted-hash.js 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/auth/verified-required.js b/api/src/auth/verified-required.js deleted file mode 100644 index 6d1cbaedb5..0000000000 --- a/api/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/src/auth/verify-jwt.js b/api/src/auth/verify-jwt.js deleted file mode 100644 index 47afc218b4..0000000000 --- a/api/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/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 index 4b9e9bce37..ee4bcc9f05 100644 --- a/api/src/create-context.js +++ b/api/src/create-context.js @@ -7,7 +7,16 @@ 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, @@ -17,6 +26,7 @@ import { checkUserIsAdminForUser, tokenize, saltedHash, + superAdminRequired, userRequired, verifiedRequired, verifyToken, @@ -26,20 +36,25 @@ import { notifyClient, sendAuthEmail, sendAuthTextMsg, + sendInviteRequestEmail, sendOrgInviteCreateAccount, sendOrgInviteEmail, sendPasswordResetEmail, - sendTfaTextMsg, + sendUpdatedUserNameEmail, sendVerificationEmail, + sendRoleChangeEmail, } from './notify' export async function createContext({ query, + db, transaction, collections, + publish, req: request, res: response, i18n, + language, loginRequiredBool, salt, }) { @@ -56,12 +71,15 @@ export async function createContext({ return { query, + db, transaction, collections, + publish, i18n, request, response, userKey, + language, moment, fetch, uuidv4, @@ -74,7 +92,12 @@ export async function createContext({ query, auth: { loginRequiredBool }, }), - checkDomainPermission: checkDomainPermission({ i18n, userKey, query }), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey, + query, + auth: { loginRequiredBool }, + }), checkOrgOwner: checkOrgOwner({ i18n, userKey, query }), checkPermission: checkPermission({ i18n, userKey, query }), checkSuperAdmin: checkSuperAdmin({ i18n, userKey, query }), @@ -88,6 +111,7 @@ export async function createContext({ tokenize, tfaRequired: tfaRequired({ i18n }), saltedHash: saltedHash(salt), + superAdminRequired: superAdminRequired({ i18n }), userRequired: userRequired({ i18n, userKey, @@ -104,14 +128,30 @@ export async function createContext({ 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 }), - sendTfaTextMsg: sendTfaTextMsg({ 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, diff --git a/api/src/create-i18n.js b/api/src/create-i18n.js index 58340f9288..863c8804d5 100644 --- a/api/src/create-i18n.js +++ b/api/src/create-i18n.js @@ -1,4 +1,3 @@ -import { en, fr } from 'make-plural/plurals' import { setupI18n } from '@lingui/core' import englishMessages from './locale/en/messages' import frenchMessages from './locale/fr/messages' @@ -11,8 +10,7 @@ export const createI18n = (language = 'en') => { }, }) - i18n.loadLocaleData('en', { plurals: en }) - i18n.loadLocaleData('fr', { plurals: fr }) + i18n.load({ en: englishMessages.messages, fr: frenchMessages.messages }) i18n.activate(language) return i18n } diff --git a/api/src/dmarc-summaries/inputs/dmarc-summary-order.js b/api/src/dmarc-summaries/inputs/dmarc-summary-order.js index fed4221efc..0df9fd9981 100644 --- a/api/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/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js b/api/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js index a55a132e4e..ef668cf691 100644 --- a/api/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js +++ b/api/src/dmarc-summaries/inputs/tests/dmarc-summary-order.test.js @@ -9,17 +9,13 @@ describe('given the dmarcSummaryOrder input object', () => { const demoType = dmarcSummaryOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(DmarcSummaryOrderField), - ) + 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/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 index 84cfd6adab..4bdaf7eda7 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,15 +13,7 @@ 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,20 +29,19 @@ describe('given the loadDkimFailConnectionsBySumId loader', () => { describe('given a successful load', () => { beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) + 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, }) @@ -453,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([ @@ -483,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([ @@ -499,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, @@ -519,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: ${ @@ -535,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, @@ -555,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: ${ @@ -587,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([ @@ -602,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, @@ -622,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([ @@ -657,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([ @@ -826,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.'), ) } @@ -856,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.'), ) } @@ -871,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, @@ -892,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([ @@ -907,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, @@ -928,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([ @@ -959,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([ @@ -974,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, @@ -994,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([ @@ -1031,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/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 index a256ec36a6..230f562303 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,15 +13,7 @@ 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,20 +30,19 @@ describe('given the loadDmarcFailConnectionsBySumId loader', () => { describe('given a successful load', () => { beforeEach(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) + 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, }) @@ -448,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.'), ) } @@ -476,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([ @@ -492,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, @@ -512,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: ${ @@ -528,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, @@ -548,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: ${ @@ -579,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([ @@ -594,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, @@ -614,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([ @@ -649,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([ @@ -813,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.'), ) } @@ -842,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.'), ) } @@ -857,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, @@ -878,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([ @@ -893,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, @@ -914,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([ @@ -944,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([ @@ -959,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, @@ -979,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([ @@ -1016,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/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 index 75e4cfb80c..6fdcaca5f3 100644 --- a/api/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 { 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,22 +31,21 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { 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, - })) + 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, }) @@ -181,9 +168,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -208,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), }, } @@ -231,9 +213,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -258,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), }, } @@ -281,9 +258,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -307,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), }, } @@ -330,9 +302,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -356,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), }, } @@ -379,10 +346,7 @@ 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, @@ -390,9 +354,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -417,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), }, } @@ -443,9 +402,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -464,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], }, @@ -477,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), }, } @@ -503,9 +451,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -524,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], }, @@ -537,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), }, } @@ -565,9 +502,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -586,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], }, @@ -599,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), }, } @@ -625,9 +551,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -646,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], }, @@ -659,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), }, } @@ -687,9 +602,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -708,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], }, @@ -721,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), }, } @@ -747,9 +651,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -768,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], }, @@ -781,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), }, } @@ -809,9 +702,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -830,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], }, @@ -843,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), }, } @@ -869,9 +751,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -890,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], }, @@ -903,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), }, } @@ -931,9 +802,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -952,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], }, @@ -965,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), }, } @@ -991,9 +851,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1012,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], }, @@ -1025,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), }, } @@ -1053,9 +902,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1074,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], }, @@ -1087,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), }, } @@ -1113,9 +951,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1134,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], }, @@ -1147,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), }, } @@ -1175,9 +1002,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1196,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], }, @@ -1209,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), }, } @@ -1235,9 +1051,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1256,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], }, @@ -1269,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), }, } @@ -1297,9 +1102,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1318,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], }, @@ -1331,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), }, } @@ -1357,9 +1151,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1378,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], }, @@ -1391,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), }, } @@ -1419,9 +1202,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1440,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], }, @@ -1453,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), }, } @@ -1479,9 +1251,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1500,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], }, @@ -1513,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), }, } @@ -1541,9 +1302,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1562,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], }, @@ -1575,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), }, } @@ -1601,9 +1351,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1622,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], }, @@ -1635,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), }, } @@ -1665,9 +1404,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1686,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], }, @@ -1699,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), }, } @@ -1725,9 +1453,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1746,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], }, @@ -1759,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), }, } @@ -1787,9 +1504,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1808,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], }, @@ -1821,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), }, } @@ -1847,9 +1553,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1868,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], }, @@ -1881,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), }, } @@ -1909,9 +1604,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1930,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], }, @@ -1943,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), }, } @@ -1969,9 +1653,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -1990,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], }, @@ -2003,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), }, } @@ -2031,9 +1704,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2052,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], }, @@ -2065,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), }, } @@ -2091,9 +1753,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2112,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], }, @@ -2125,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), }, } @@ -2153,9 +1804,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2174,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], }, @@ -2187,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), }, } @@ -2213,9 +1853,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2234,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], }, @@ -2247,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), }, } @@ -2275,9 +1904,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2296,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], }, @@ -2309,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), }, } @@ -2335,9 +1953,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2356,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], }, @@ -2369,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), }, } @@ -2397,9 +2004,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2418,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], }, @@ -2431,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), }, } @@ -2457,9 +2053,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2478,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], }, @@ -2491,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), }, } @@ -2519,9 +2104,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2540,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], }, @@ -2553,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), }, } @@ -2579,9 +2153,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2600,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], }, @@ -2613,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), }, } @@ -2641,9 +2204,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2662,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], }, @@ -2675,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), }, } @@ -2701,9 +2253,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2722,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], }, @@ -2735,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), }, } @@ -2763,9 +2304,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2784,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], }, @@ -2797,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), }, } @@ -2823,9 +2353,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2844,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], }, @@ -2857,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), }, } @@ -2886,9 +2405,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n: {}, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -2919,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), }, } @@ -2992,9 +2557,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3026,9 +2589,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3063,9 +2624,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3098,9 +2657,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3135,9 +2692,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3151,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.`, @@ -3170,9 +2721,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3186,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.`, @@ -3201,18 +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 = { @@ -3226,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: ${ @@ -3242,18 +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 = { @@ -3267,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: ${ @@ -3290,9 +2819,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3306,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([ @@ -3324,9 +2849,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3340,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([ @@ -3353,9 +2874,7 @@ 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, @@ -3363,9 +2882,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3378,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([ @@ -3407,9 +2920,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3422,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([ @@ -3461,9 +2968,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3495,9 +3000,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3532,9 +3035,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3567,9 +3068,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3604,9 +3103,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3621,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([ @@ -3639,9 +3134,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3656,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([ @@ -3670,18 +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 = { @@ -3696,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([ @@ -3711,18 +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 = { @@ -3737,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([ @@ -3759,9 +3238,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3775,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([ @@ -3793,9 +3268,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3809,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([ @@ -3822,9 +3293,7 @@ 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, @@ -3832,9 +3301,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3848,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.'), ) } @@ -3876,9 +3341,7 @@ describe('given the loadDmarcSummaryConnectionsByUserId function', () => { cleanseInput, auth: { loginRequired: true }, i18n, - loadStartDateFromPeriod: jest - .fn() - .mockReturnValueOnce('thirtyDays'), + loadStartDateFromPeriod: jest.fn().mockReturnValueOnce('thirtyDays'), }) const connectionArgs = { @@ -3892,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/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 index c434fd36ef..e0661ce692 100644 --- a/api/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,4 +1,5 @@ -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' @@ -26,26 +27,25 @@ describe('given the loadDmarcSummaryEdgeByDomainIdAndPeriod loader', () => { 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, - })) + 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: [], @@ -111,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`, ]) @@ -145,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.`, ]) @@ -186,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`, ]) @@ -222,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/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 index 026dd709c2..33626679d4 100644 --- a/api/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,4 +1,5 @@ -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' @@ -9,14 +10,7 @@ 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,16 +24,16 @@ describe('given the loadDmarcSummaryByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { domain = await collections.domains.save({ @@ -108,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, @@ -131,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) }) @@ -148,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, @@ -169,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) }) @@ -194,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.`, @@ -222,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([ @@ -254,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.`, @@ -284,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/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 index a445adf057..16bc47873f 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,15 +13,7 @@ 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,26 +30,25 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { 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, - })) + 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', @@ -68,7 +60,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', id: 1, } - + fullPass2 = { sourceIpAddress: '123.456.78.91', envelopeFrom: 'envelope.from', @@ -80,7 +72,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', id: 2, } - + dmarcSummary = await collections.dmarcSummaries.save({ detailTables: { dkimFailure: [], @@ -116,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: [ { @@ -143,7 +135,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -155,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: [ { @@ -182,7 +174,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -194,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: [ { @@ -220,7 +212,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -232,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: [ { @@ -258,7 +250,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: toGlobalId('fullPass', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -266,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, @@ -291,7 +283,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { endCursor: '', }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -321,7 +313,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -336,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.`, ]) @@ -350,7 +342,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -367,7 +359,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadFullPassConnectionsBySumId.`, ]) @@ -382,7 +374,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -398,7 +390,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -412,7 +404,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -428,7 +420,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -444,7 +436,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -454,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.`, ]) @@ -474,7 +462,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -484,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.`, ]) @@ -500,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: ${ @@ -536,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: ${ @@ -579,7 +551,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 1, } @@ -588,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.`, ]) @@ -601,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: '', @@ -621,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.`, ]) @@ -639,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: '', @@ -656,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.`, ]) @@ -691,7 +655,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -706,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.`, ]) @@ -720,7 +684,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -737,7 +701,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadFullPassConnectionsBySumId.`, ]) @@ -752,7 +716,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -768,7 +732,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -782,7 +746,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -798,7 +762,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadFullPassConnectionsBySumId.`, ]) @@ -814,7 +778,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -825,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.`, ]) @@ -844,7 +806,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -854,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.`, ]) @@ -870,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([ @@ -906,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([ @@ -949,7 +899,7 @@ describe('given the loadFullPassConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 1, } @@ -959,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.`, ]) @@ -973,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: '', @@ -994,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.`, ]) @@ -1013,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: '', @@ -1031,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/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 index 0461ca7201..a781d5f949 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,15 +13,7 @@ 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,26 +30,25 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { 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, - })) + 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', @@ -69,7 +61,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', guidance: '', } - + spfFailure2 = { sourceIpAddress: '123.456.78.91', envelopeFrom: 'envelope.from', @@ -82,7 +74,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { dnsHost: 'dns.host.ca', guidance: '', } - + dmarcSummary = await collections.dmarcSummaries.save({ detailTables: { dkimFailure: [], @@ -118,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: [ { @@ -145,7 +137,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -157,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: [ { @@ -184,7 +176,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -196,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: [ { @@ -222,7 +214,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 1), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -234,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: [ { @@ -260,7 +252,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: toGlobalId('spfFail', 2), }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -268,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, @@ -293,7 +285,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { endCursor: '', }, } - + expect(summaries).toEqual(expectedStructure) }) }) @@ -323,7 +315,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -338,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.`, ]) @@ -352,7 +344,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -369,7 +361,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfFailureConnectionsBySumId.`, ]) @@ -384,7 +376,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -400,7 +392,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -414,7 +406,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -430,7 +422,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -446,7 +438,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -456,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.`, ]) @@ -476,7 +464,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -486,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.`, ]) @@ -502,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: ${ @@ -538,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: ${ @@ -581,7 +553,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 10, } @@ -590,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.`, ]) @@ -603,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: '', @@ -623,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.`, ]) @@ -641,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: '', @@ -658,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.`, ]) @@ -693,7 +657,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { summaryId: '', } @@ -708,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.`, ]) @@ -722,7 +686,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 1, last: 1, @@ -739,7 +703,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` and \`last\` arguments set for: loadSpfFailureConnectionsBySumId.`, ]) @@ -754,7 +718,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 101, summaryId: '', @@ -770,7 +734,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`first\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -784,7 +748,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: 101, summaryId: '', @@ -800,7 +764,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { ), ) } - + expect(consoleOutput).toEqual([ `User: ${user._key} attempted to have \`last\` set to 101 for: loadSpfFailureConnectionsBySumId.`, ]) @@ -816,7 +780,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: -1, summaryId: '', @@ -827,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.`, ]) @@ -846,7 +808,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { last: -1, summaryId: '', @@ -857,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.`, ]) @@ -872,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([ @@ -908,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([ @@ -951,7 +903,7 @@ describe('given the loadSpfFailureConnectionsBySumId loader', () => { cleanseInput, i18n, }) - + const connectionArgs = { first: 10, } @@ -960,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.`, ]) @@ -975,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: '', @@ -995,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.`, ]) @@ -1015,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: '', @@ -1032,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/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 index 31c0c7d6a2..38bd63605e 100644 --- a/api/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,4 +1,5 @@ -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' @@ -9,15 +10,7 @@ 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,22 +28,21 @@ describe('given the loadDmarcYearlySumEdge loader', () => { describe('given a successful load', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -154,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, @@ -170,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([ @@ -201,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([ @@ -229,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, @@ -245,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([ @@ -278,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 index a8a6b12b1d..c5cb745484 100644 --- a/api/src/dmarc-summaries/loaders/index.js +++ b/api/src/dmarc-summaries/loaders/index.js @@ -7,3 +7,4 @@ 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/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 index 146b46ae5e..941985cf69 100644 --- a/api/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 @@ -2,410 +2,383 @@ import { aql } from 'arangojs' import { fromGlobalId, toGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' -export const loadDmarcSummaryConnectionsByUserId = ({ - query, - userKey, - cleanseInput, - i18n, - loadStartDateFromPeriod, - auth: { loginRequiredBool }, -}) => async ({ - after, - before, - first, - last, - period, - year, - orderBy, - isSuperAdmin, - search, -}) => { - 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.`, - ), - ) - } - const cleansedPeriod = cleanseInput(period) +export const loadDmarcSummaryConnectionsByUserId = + ({ query, userKey, cleanseInput, i18n, loadStartDateFromPeriod }) => + 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.`)) + } + 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.`, - ), - ) - } - const cleansedYear = cleanseInput(year) - - const startDate = loadStartDateFromPeriod({ - period: cleansedPeriod, - year: cleansedYear, - }) - - 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(summary._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` + 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.`)) + } + const cleansedYear = cleanseInput(year) + + const startDate = loadStartDateFromPeriod({ + period: cleansedPeriod, + year: cleansedYear, + }) + + 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(summary._key) > TO_NUMBER(${afterId})` } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(dmarcSummaries, ${afterId})` + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } - let documentField = aql`` - let summaryField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - documentField = aql`afterVar.categoryTotals.fail` - summaryField = aql`summary.categoryTotals.fail` - } else if (orderBy.field === 'pass-count') { - documentField = aql`afterVar.categoryTotals.pass` - summaryField = aql`summary.categoryTotals.pass` - } else if (orderBy.field === 'pass-dkim-count') { - documentField = aql`afterVar.categoryTotals.passDkimOnly` - summaryField = aql`summary.categoryTotals.passDkimOnly` - } else if (orderBy.field === 'pass-spf-count') { - documentField = aql`afterVar.categoryTotals.passSpfOnly` - summaryField = aql`summary.categoryTotals.passSpfOnly` - } else if (orderBy.field === 'fail-percentage') { - documentField = aql`afterVar.categoryPercentages.fail` - summaryField = aql`summary.categoryPercentages.fail` - } else if (orderBy.field === 'pass-percentage') { - documentField = aql`afterVar.categoryPercentages.pass` - summaryField = aql`summary.categoryPercentages.pass` - } else if (orderBy.field === 'pass-dkim-percentage') { - documentField = aql`afterVar.categoryPercentages.passDkimOnly` - summaryField = aql`summary.categoryPercentages.passDkimOnly` - } else if (orderBy.field === 'pass-spf-percentage') { - documentField = aql`afterVar.categoryPercentages.passSpfOnly` - summaryField = aql`summary.categoryPercentages.passSpfOnly` - } else if (orderBy.field === 'total-messages') { - documentField = aql`afterVar.totalMessages` - summaryField = aql`summary.totalMessages` - } else if (orderBy.field === 'domain') { - documentField = aql` + afterVar = aql`LET afterVar = DOCUMENT(dmarcSummaries, ${afterId})` + + let documentField = aql`` + let summaryField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + documentField = aql`afterVar.categoryTotals.fail` + summaryField = aql`summary.categoryTotals.fail` + } else if (orderBy.field === 'pass-count') { + documentField = aql`afterVar.categoryTotals.pass` + summaryField = aql`summary.categoryTotals.pass` + } else if (orderBy.field === 'pass-dkim-count') { + documentField = aql`afterVar.categoryTotals.passDkimOnly` + summaryField = aql`summary.categoryTotals.passDkimOnly` + } else if (orderBy.field === 'pass-spf-count') { + documentField = aql`afterVar.categoryTotals.passSpfOnly` + summaryField = aql`summary.categoryTotals.passSpfOnly` + } else if (orderBy.field === 'fail-percentage') { + documentField = aql`afterVar.categoryPercentages.fail` + summaryField = aql`summary.categoryPercentages.fail` + } else if (orderBy.field === 'pass-percentage') { + documentField = aql`afterVar.categoryPercentages.pass` + summaryField = aql`summary.categoryPercentages.pass` + } else if (orderBy.field === 'pass-dkim-percentage') { + documentField = aql`afterVar.categoryPercentages.passDkimOnly` + summaryField = aql`summary.categoryPercentages.passDkimOnly` + } else if (orderBy.field === 'pass-spf-percentage') { + documentField = aql`afterVar.categoryPercentages.passSpfOnly` + summaryField = aql`summary.categoryPercentages.passSpfOnly` + } else if (orderBy.field === 'total-messages') { + documentField = aql`afterVar.totalMessages` + summaryField = aql`summary.totalMessages` + } else if (orderBy.field === 'domain') { + documentField = aql` FIRST( FOR v, e IN 1..1 ANY afterVar._id domainsToDmarcSummaries RETURN v.domain ) ` - summaryField = aql`domain.domain` - } + summaryField = aql`domain.domain` + } - afterTemplate = aql` + afterTemplate = aql` FILTER ${summaryField} ${afterTemplateDirection} ${documentField} OR (${summaryField} == ${documentField} AND TO_NUMBER(summary._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(summary._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` + 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(summary._key) < TO_NUMBER(${beforeId})` } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(dmarcSummaries, ${beforeId})` + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } - let documentField = aql`` - let summaryField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - documentField = aql`beforeVar.categoryTotals.fail` - summaryField = aql`summary.categoryTotals.fail` - } else if (orderBy.field === 'pass-count') { - documentField = aql`beforeVar.categoryTotals.pass` - summaryField = aql`summary.categoryTotals.pass` - } else if (orderBy.field === 'pass-dkim-count') { - documentField = aql`beforeVar.categoryTotals.passDkimOnly` - summaryField = aql`summary.categoryTotals.passDkimOnly` - } else if (orderBy.field === 'pass-spf-count') { - documentField = aql`beforeVar.categoryTotals.passSpfOnly` - summaryField = aql`summary.categoryTotals.passSpfOnly` - } else if (orderBy.field === 'fail-percentage') { - documentField = aql`beforeVar.categoryPercentages.fail` - summaryField = aql`summary.categoryPercentages.fail` - } else if (orderBy.field === 'pass-percentage') { - documentField = aql`beforeVar.categoryPercentages.pass` - summaryField = aql`summary.categoryPercentages.pass` - } else if (orderBy.field === 'pass-dkim-percentage') { - documentField = aql`beforeVar.categoryPercentages.passDkimOnly` - summaryField = aql`summary.categoryPercentages.passDkimOnly` - } else if (orderBy.field === 'pass-spf-percentage') { - documentField = aql`beforeVar.categoryPercentages.passSpfOnly` - summaryField = aql`summary.categoryPercentages.passSpfOnly` - } else if (orderBy.field === 'total-messages') { - documentField = aql`beforeVar.totalMessages` - summaryField = aql`summary.totalMessages` - } else if (orderBy.field === 'domain') { - documentField = aql` + beforeVar = aql`LET beforeVar = DOCUMENT(dmarcSummaries, ${beforeId})` + + let documentField = aql`` + let summaryField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + documentField = aql`beforeVar.categoryTotals.fail` + summaryField = aql`summary.categoryTotals.fail` + } else if (orderBy.field === 'pass-count') { + documentField = aql`beforeVar.categoryTotals.pass` + summaryField = aql`summary.categoryTotals.pass` + } else if (orderBy.field === 'pass-dkim-count') { + documentField = aql`beforeVar.categoryTotals.passDkimOnly` + summaryField = aql`summary.categoryTotals.passDkimOnly` + } else if (orderBy.field === 'pass-spf-count') { + documentField = aql`beforeVar.categoryTotals.passSpfOnly` + summaryField = aql`summary.categoryTotals.passSpfOnly` + } else if (orderBy.field === 'fail-percentage') { + documentField = aql`beforeVar.categoryPercentages.fail` + summaryField = aql`summary.categoryPercentages.fail` + } else if (orderBy.field === 'pass-percentage') { + documentField = aql`beforeVar.categoryPercentages.pass` + summaryField = aql`summary.categoryPercentages.pass` + } else if (orderBy.field === 'pass-dkim-percentage') { + documentField = aql`beforeVar.categoryPercentages.passDkimOnly` + summaryField = aql`summary.categoryPercentages.passDkimOnly` + } else if (orderBy.field === 'pass-spf-percentage') { + documentField = aql`beforeVar.categoryPercentages.passSpfOnly` + summaryField = aql`summary.categoryPercentages.passSpfOnly` + } else if (orderBy.field === 'total-messages') { + documentField = aql`beforeVar.totalMessages` + summaryField = aql`summary.totalMessages` + } else if (orderBy.field === 'domain') { + documentField = aql` FIRST( FOR v, e IN 1..1 ANY beforeVar._id domainsToDmarcSummaries RETURN v.domain ) ` - summaryField = aql`domain.domain` - } + summaryField = aql`domain.domain` + } - beforeTemplate = aql` + beforeTemplate = aql` FILTER ${summaryField} ${beforeTemplateDirection} ${documentField} OR (${summaryField} == ${documentField} AND TO_NUMBER(summary._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: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`DmarcSummaries\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `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.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcSummaryConnectionsByUserId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDmarcSummaryConnectionsByUserId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`DmarcSummaries\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`DmarcSummaries\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcSummaryConnectionsByUserId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcSummaryConnectionsByUserId.`, ) throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`DmarcSummaries\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`DmarcSummaries\` connection is not supported.`), ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(summary._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(summary._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: loadDmarcSummaryConnectionsByUserId.`, - ) - 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)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(summary._key) < TO_NUMBER(FIRST(retrievedSummaries)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` + } 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: loadDmarcSummaryConnectionsByUserId.`, + ) + 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 + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcSummaryConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`DmarcSummaries\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(summary._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(summary._key) DESC LIMIT TO_NUMBER(${last})` + } } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + 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: loadDmarcSummaryConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) } - let hasNextPageDocumentField = aql`` - let summaryField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - summaryField = aql`summary.categoryTotals.fail` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.fail` - } else if (orderBy.field === 'pass-count') { - summaryField = aql`summary.categoryTotals.pass` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.pass` - } else if (orderBy.field === 'pass-dkim-count') { - summaryField = aql`summary.categoryTotals.passDkimOnly` - hasNextPageDocumentField = aql` LAST(retrievedSummaries).categoryTotals.passDkimOnly` - hasPreviousPageDocumentField = aql` FIRST(retrievedSummaries).categoryTotals.passDkimOnly` - } else if (orderBy.field === 'pass-spf-count') { - summaryField = aql`summary.categoryTotals.passSpfOnly` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.passSpfOnly` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.passSpfOnly` - } else if (orderBy.field === 'fail-percentage') { - summaryField = aql`summary.categoryPercentages.fail` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.fail` - } else if (orderBy.field === 'pass-percentage') { - summaryField = aql`summary.categoryPercentages.pass` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.pass` - } else if (orderBy.field === 'pass-dkim-percentage') { - summaryField = aql`summary.categoryPercentages.passDkimOnly` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passDkimOnly` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passDkimOnly` - } else if (orderBy.field === 'pass-spf-percentage') { - summaryField = aql`summary.categoryPercentages.passSpfOnly` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passSpfOnly` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passSpfOnly` - } else if (orderBy.field === 'total-messages') { - summaryField = aql`summary.totalMessages` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).totalMessages` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).totalMessages` - } else if (orderBy.field === 'domain') { - summaryField = aql`domain.domain` - hasNextPageDocumentField = aql` + let hasNextPageFilter = aql`FILTER TO_NUMBER(summary._key) > TO_NUMBER(LAST(retrievedSummaries)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(summary._key) < TO_NUMBER(FIRST(retrievedSummaries)._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 hasNextPageDocumentField = aql`` + let summaryField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + summaryField = aql`summary.categoryTotals.fail` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.fail` + } else if (orderBy.field === 'pass-count') { + summaryField = aql`summary.categoryTotals.pass` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.pass` + } else if (orderBy.field === 'pass-dkim-count') { + summaryField = aql`summary.categoryTotals.passDkimOnly` + hasNextPageDocumentField = aql` LAST(retrievedSummaries).categoryTotals.passDkimOnly` + hasPreviousPageDocumentField = aql` FIRST(retrievedSummaries).categoryTotals.passDkimOnly` + } else if (orderBy.field === 'pass-spf-count') { + summaryField = aql`summary.categoryTotals.passSpfOnly` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.passSpfOnly` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.passSpfOnly` + } else if (orderBy.field === 'fail-percentage') { + summaryField = aql`summary.categoryPercentages.fail` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.fail` + } else if (orderBy.field === 'pass-percentage') { + summaryField = aql`summary.categoryPercentages.pass` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.pass` + } else if (orderBy.field === 'pass-dkim-percentage') { + summaryField = aql`summary.categoryPercentages.passDkimOnly` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passDkimOnly` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passDkimOnly` + } else if (orderBy.field === 'pass-spf-percentage') { + summaryField = aql`summary.categoryPercentages.passSpfOnly` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passSpfOnly` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passSpfOnly` + } else if (orderBy.field === 'total-messages') { + summaryField = aql`summary.totalMessages` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).totalMessages` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).totalMessages` + } else if (orderBy.field === 'domain') { + summaryField = aql`domain.domain` + hasNextPageDocumentField = aql` FIRST( FOR v, e IN 1..1 ANY LAST(retrievedSummaries)._id domainsToDmarcSummaries RETURN v.domain ) ` - hasPreviousPageDocumentField = aql` + hasPreviousPageDocumentField = aql` FIRST( FOR v, e IN 1..1 ANY FIRST(retrievedSummaries)._id domainsToDmarcSummaries RETURN v.domain ) ` - } + } - hasNextPageFilter = aql` + hasNextPageFilter = aql` FILTER ${summaryField} ${hasNextPageDirection} ${hasNextPageDocumentField} OR (${summaryField} == ${hasNextPageDocumentField} AND TO_NUMBER(summary._key) > TO_NUMBER(LAST(retrievedSummaries)._key)) ` - hasPreviousPageFilter = aql` + hasPreviousPageFilter = aql` FILTER ${summaryField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} OR (${summaryField} == ${hasPreviousPageDocumentField} AND TO_NUMBER(summary._key) < TO_NUMBER(FIRST(retrievedSummaries)._key)) ` - } + } - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - sortByField = aql`summary.categoryTotals.fail ${orderBy.direction},` - } else if (orderBy.field === 'pass-count') { - sortByField = aql`summary.categoryTotals.pass ${orderBy.direction},` - } else if (orderBy.field === 'pass-dkim-count') { - sortByField = aql`summary.categoryTotals.passDkimOnly ${orderBy.direction},` - } else if (orderBy.field === 'pass-spf-count') { - sortByField = aql`summary.categoryTotals.passSpfOnly ${orderBy.direction},` - } else if (orderBy.field === 'fail-percentage') { - sortByField = aql`summary.categoryPercentages.fail ${orderBy.direction},` - } else if (orderBy.field === 'pass-percentage') { - sortByField = aql`summary.categoryPercentages.pass ${orderBy.direction},` - } else if (orderBy.field === 'pass-dkim-percentage') { - sortByField = aql`summary.categoryPercentages.passDkimOnly ${orderBy.direction},` - } else if (orderBy.field === 'pass-spf-percentage') { - sortByField = aql`summary.categoryPercentages.passSpfOnly ${orderBy.direction},` - } else if (orderBy.field === 'total-messages') { - sortByField = aql`summary.totalMessages ${orderBy.direction},` - } else if (orderBy.field === 'domain') { - sortByField = aql`domain.domain ${orderBy.direction},` + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + sortByField = aql`summary.categoryTotals.fail ${orderBy.direction},` + } else if (orderBy.field === 'pass-count') { + sortByField = aql`summary.categoryTotals.pass ${orderBy.direction},` + } else if (orderBy.field === 'pass-dkim-count') { + sortByField = aql`summary.categoryTotals.passDkimOnly ${orderBy.direction},` + } else if (orderBy.field === 'pass-spf-count') { + sortByField = aql`summary.categoryTotals.passSpfOnly ${orderBy.direction},` + } else if (orderBy.field === 'fail-percentage') { + sortByField = aql`summary.categoryPercentages.fail ${orderBy.direction},` + } else if (orderBy.field === 'pass-percentage') { + sortByField = aql`summary.categoryPercentages.pass ${orderBy.direction},` + } else if (orderBy.field === 'pass-dkim-percentage') { + sortByField = aql`summary.categoryPercentages.passDkimOnly ${orderBy.direction},` + } else if (orderBy.field === 'pass-spf-percentage') { + sortByField = aql`summary.categoryPercentages.passSpfOnly ${orderBy.direction},` + } else if (orderBy.field === 'total-messages') { + sortByField = aql`summary.totalMessages ${orderBy.direction},` + } else if (orderBy.field === 'domain') { + sortByField = aql`domain.domain ${orderBy.direction},` + } } - } - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let domainQuery = aql`` - let searchDomainFilter = aql`` - if (typeof search !== 'undefined') { - 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` - } + let searchDomainFilter = aql`` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + searchDomainFilter = aql`FILTER userDomain.domain LIKE LOWER(CONCAT("%", ${search}, "%"))` + } - let domainIdQueries - if (isSuperAdmin || !loginRequiredBool) { - domainIdQueries = aql` + let domainQueries + if (isSuperAdmin) { + 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 { - domainIdQueries = aql` + } else if (isAffiliated) { + 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 userDomains = UNIQUE( + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key + FOR v, e IN 1..1 OUTBOUND org._id ownership + RETURN v + ) ` - } - - let requestedSummaryInfo - try { - requestedSummaryInfo = await query` - ${domainIdQueries} + } else { + 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 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 + ) + ` + } - ${domainQuery} + let requestedSummaryInfo + try { + requestedSummaryInfo = await query` + ${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 ) @@ -469,56 +442,52 @@ export const loadDmarcSummaryConnectionsByUserId = ({ "endKey": LAST(retrievedSummaries)._key } ` - } catch (err) { - 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.`), - ) - } + } catch (err) { + 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.`)) + } - let summariesInfo - try { - summariesInfo = await requestedSummaryInfo.next() - } catch (err) { - 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.`), - ) - } + let summariesInfo + try { + summariesInfo = await requestedSummaryInfo.next() + } catch (err) { + 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.`)) + } - if (summariesInfo.summaries.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, + if (summariesInfo.summaries.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } } - } - const edges = summariesInfo.summaries.map((summary) => { - summary.startDate = startDate + const edges = summariesInfo.summaries.map((summary) => { + summary.startDate = startDate + return { + cursor: toGlobalId('dmarcSummary', summary.id), + node: summary, + } + }) + return { - cursor: toGlobalId('dmarcSummary', summary.id), - node: summary, + edges, + totalCount: summariesInfo.totalCount, + pageInfo: { + hasNextPage: summariesInfo.hasNextPage, + hasPreviousPage: summariesInfo.hasPreviousPage, + startCursor: toGlobalId('dmarcSummary', summariesInfo.startKey), + endCursor: toGlobalId('dmarcSummary', summariesInfo.endKey), + }, } - }) - - return { - edges, - totalCount: summariesInfo.totalCount, - pageInfo: { - hasNextPage: summariesInfo.hasNextPage, - hasPreviousPage: summariesInfo.hasPreviousPage, - startCursor: toGlobalId('dmarcSummary', summariesInfo.startKey), - endCursor: toGlobalId('dmarcSummary', summariesInfo.endKey), - }, } -} diff --git a/api/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js b/api/src/dmarc-summaries/objects/__tests__/dkim-failure-table.test.js index 245a901a9c..133de70e0f 100644 --- a/api/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/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js b/api/src/dmarc-summaries/objects/__tests__/dmarc-failure-table.test.js index 23660b64ba..770eea2cf6 100644 --- a/api/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/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js b/api/src/dmarc-summaries/objects/__tests__/dmarc-summary.test.js index cc437758c9..ed0b50fcba 100644 --- a/api/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/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js b/api/src/dmarc-summaries/objects/__tests__/full-pass-table.test.js index 1f03b5b728..c8ba45c2e0 100644 --- a/api/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/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js b/api/src/dmarc-summaries/objects/__tests__/spf-failure-table.test.js index f44a67b3f2..c35d341b5b 100644 --- a/api/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/src/dmarc-summaries/objects/dkim-failure-table.js b/api/src/dmarc-summaries/objects/dkim-failure-table.js index 950f312431..140022b472 100644 --- a/api/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/src/dmarc-summaries/objects/spf-failure-table.js b/api/src/dmarc-summaries/objects/spf-failure-table.js index 19e1e34cd3..f15cddf528 100644 --- a/api/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/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js b/api/src/dmarc-summaries/queries/__tests__/find-my-dmarc-summaries.test.js index 195d4f7b8f..9c837bbe8a 100644 --- a/api/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,5 +1,6 @@ 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' @@ -17,16 +18,7 @@ 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,23 +57,22 @@ describe('given the findMyDmarcSummaries query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { mockedStartDateLoader = jest.fn().mockReturnValue('2021-01-01') user = await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: true, }) @@ -160,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 { @@ -183,8 +174,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: user._key, @@ -202,19 +193,17 @@ describe('given the findMyDmarcSummaries query', () => { verifiedRequired: verifiedRequired({ i18n }), }, loaders: { - loadDmarcSummaryConnectionsByUserId: loadDmarcSummaryConnectionsByUserId( - { - query, - userKey: user._key, - cleanseInput, - auth: { loginRequired: true }, - i18n, - loadStartDateFromPeriod: mockedStartDateLoader, - }, - ), + loadDmarcSummaryConnectionsByUserId: loadDmarcSummaryConnectionsByUserId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequired: true }, + i18n, + loadStartDateFromPeriod: mockedStartDateLoader, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -241,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', () => { @@ -264,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 { @@ -287,8 +274,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: undefined, @@ -308,22 +295,18 @@ describe('given the findMyDmarcSummaries query', () => { 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 { @@ -344,8 +327,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: user._key, @@ -355,26 +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, - auth: { loginRequired: true }, - 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([ @@ -400,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 { @@ -423,8 +398,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: undefined, @@ -444,24 +419,18 @@ describe('given the findMyDmarcSummaries query', () => { 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 { @@ -482,8 +451,8 @@ describe('given the findMyDmarcSummaries query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, moment, userKey: user._key, @@ -493,26 +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, - auth: { loginRequired: true }, - 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/src/dmarc-summaries/queries/find-my-dmarc-summaries.js b/api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js index d1259ad88f..cda2e995ae 100644 --- a/api/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, }, @@ -34,19 +37,12 @@ export const findMyDmarcSummaries = { args, { userKey, - auth: { - checkSuperAdmin, - userRequired, - verifiedRequired, - loginRequiredBool, - }, + auth: { checkSuperAdmin, userRequired, verifiedRequired }, loaders: { loadDmarcSummaryConnectionsByUserId }, }, ) => { - if (loginRequiredBool) { - const user = await userRequired() - verifiedRequired({ user }) - } + 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 index 94bbe87479..790f25d215 100644 --- a/api/src/dmarc-summaries/queries/index.js +++ b/api/src/dmarc-summaries/queries/index.js @@ -1 +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/src/domain/inputs/__tests__/domain-order.test.js b/api/src/domain/inputs/__tests__/domain-order.test.js index 4ef512e427..298e193e85 100644 --- a/api/src/domain/inputs/__tests__/domain-order.test.js +++ b/api/src/domain/inputs/__tests__/domain-order.test.js @@ -9,17 +9,13 @@ describe('given the domainOrder input object', () => { const demoType = domainOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(DomainOrderField), - ) + 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/src/domain/inputs/domain-order.js b/api/src/domain/inputs/domain-order.js index e37afbebf2..c18a19b10e 100644 --- a/api/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 index 2dc77b3ff7..a1b014c4b5 100644 --- a/api/src/domain/inputs/index.js +++ b/api/src/domain/inputs/index.js @@ -1 +1,2 @@ +export * from './domain-filter' export * from './domain-order' diff --git a/api/src/domain/loaders/__tests__/load-domain-by-domain.test.js b/api/src/domain/loaders/__tests__/load-domain-by-domain.test.js index 96c764080c..2e43e661e8 100644 --- a/api/src/domain/loaders/__tests__/load-domain-by-domain.test.js +++ b/api/src/domain/loaders/__tests__/load-domain-by-domain.test.js @@ -1,4 +1,5 @@ -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' @@ -21,16 +22,16 @@ describe('given a loadDomainByDomain dataloader', () => { 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, - })) + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) }) beforeEach(async () => { await collections.domains.save({ @@ -100,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', @@ -112,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([ @@ -139,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([ @@ -167,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', @@ -179,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([ @@ -206,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/src/domain/loaders/__tests__/load-domain-by-key.test.js b/api/src/domain/loaders/__tests__/load-domain-by-key.test.js index 24aa84f959..761eca50ea 100644 --- a/api/src/domain/loaders/__tests__/load-domain-by-key.test.js +++ b/api/src/domain/loaders/__tests__/load-domain-by-key.test.js @@ -1,4 +1,5 @@ -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' @@ -23,16 +24,16 @@ describe('given a loadDomainByKey dataloader', () => { 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, - })) + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) }) beforeEach(async () => { await collections.domains.save({ @@ -104,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', @@ -116,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([ @@ -143,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([ @@ -171,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', @@ -183,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([ @@ -210,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/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js b/api/src/domain/loaders/__tests__/load-domain-conn-org-id.test.js index a9c0baf4a0..b6551a8595 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,7 +13,7 @@ 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,22 +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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) + 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, }) @@ -88,10 +89,13 @@ 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', @@ -104,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 () => { @@ -121,15 +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 @@ -149,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', }, }, ], @@ -168,16 +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 @@ -197,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', }, }, ], @@ -216,16 +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 @@ -244,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', }, }, ], @@ -264,15 +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 @@ -291,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', }, }, ], @@ -320,6 +327,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -341,6 +349,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomain._key), node: { ...expectedDomain, + claimTags: [], + assetState: 'approved', }, }, ], @@ -362,6 +372,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -397,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, @@ -408,14 +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 @@ -434,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', }, }, ], @@ -454,16 +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, @@ -480,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', }, }, ], @@ -514,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 @@ -533,6 +547,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -547,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', }, }, ], @@ -565,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 @@ -584,6 +598,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -599,6 +614,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -619,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 @@ -638,6 +652,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -653,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', }, }, ], @@ -671,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 @@ -690,6 +704,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -705,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', }, }, ], @@ -725,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 @@ -744,6 +758,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -759,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', }, }, ], @@ -777,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 @@ -796,6 +810,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -811,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', }, }, ], @@ -831,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 @@ -850,6 +864,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -865,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', }, }, ], @@ -883,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 @@ -902,6 +916,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -917,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', }, }, ], @@ -937,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 @@ -956,6 +970,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -971,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', }, }, ], @@ -989,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 @@ -1008,6 +1022,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1023,6 +1038,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[1]._key), node: { ...expectedDomains[1], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1045,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 @@ -1064,6 +1078,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1079,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', }, }, ], @@ -1097,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 @@ -1116,6 +1130,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1131,6 +1146,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1151,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 @@ -1170,6 +1184,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1185,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', }, }, ], @@ -1203,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 @@ -1222,6 +1236,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1237,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', }, }, ], @@ -1257,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 @@ -1276,6 +1290,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1291,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', }, }, ], @@ -1309,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 @@ -1328,6 +1342,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1343,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', }, }, ], @@ -1363,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 @@ -1382,6 +1396,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1397,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', }, }, ], @@ -1415,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 @@ -1434,6 +1448,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1449,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', }, }, ], @@ -1469,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 @@ -1488,6 +1502,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1503,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', }, }, ], @@ -1521,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 @@ -1540,6 +1554,7 @@ describe('given the load domain connection using org id function', () => { const connectionLoader = loadDomainConnectionsByOrgId({ query, userKey: user._key, + language, cleanseInput, auth: { loginRequired: true }, }) @@ -1555,6 +1570,8 @@ describe('given the load domain connection using org id function', () => { cursor: toGlobalId('domain', expectedDomains[0]._key), node: { ...expectedDomains[0], + claimTags: [], + assetState: 'approved', }, }, ], @@ -1640,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.`), ) } @@ -1671,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([ @@ -1702,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([ @@ -1782,9 +1789,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 = loadDomainConnectionsByOrgId({ query, userKey: user._key, @@ -1803,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: ${ @@ -1819,9 +1820,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 = loadDomainConnectionsByOrgId({ query, userKey: user._key, @@ -1840,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: ${ @@ -1859,9 +1854,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 = loadDomainConnectionsByOrgId({ query, @@ -1880,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([ @@ -1918,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([ @@ -1996,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é."), ) } @@ -2027,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([ @@ -2058,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([ @@ -2138,9 +2117,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 = loadDomainConnectionsByOrgId({ query, userKey: user._key, @@ -2160,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([ @@ -2175,9 +2150,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 = loadDomainConnectionsByOrgId({ query, userKey: user._key, @@ -2197,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([ @@ -2215,9 +2186,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 = loadDomainConnectionsByOrgId({ query, @@ -2236,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([ @@ -2276,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/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 index 8ce0c41875..0286f4ae01 100644 --- a/api/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,5 +1,6 @@ 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' @@ -29,22 +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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) + 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, }) @@ -127,10 +127,7 @@ describe('given the load domain connections by user id function', () => { }) 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 @@ -172,10 +169,7 @@ describe('given the load domain connections by user id function', () => { }) 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 @@ -217,10 +211,7 @@ describe('given the load domain connections by user id function', () => { }) 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 @@ -261,10 +252,7 @@ describe('given the load domain connections by user id function', () => { }) 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 @@ -371,9 +359,7 @@ describe('given the load domain connections by user id function', () => { }) const domainLoader = loadDomainByKey({ query }) - const expectedDomains = await domainLoader.loadMany([ - domainThree._key, - ]) + const expectedDomains = await domainLoader.loadMany([domainThree._key]) const connectionArgs = { first: 1, @@ -412,11 +398,7 @@ describe('given the load domain connections by user id function', () => { }) 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, @@ -464,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 @@ -512,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 @@ -562,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 @@ -610,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 @@ -660,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 @@ -708,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 @@ -758,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 @@ -806,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 @@ -856,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 @@ -904,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 @@ -956,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 @@ -1004,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 @@ -1054,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 @@ -1102,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 @@ -1152,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 @@ -1200,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 @@ -1250,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 @@ -1298,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 @@ -1348,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 @@ -1396,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 @@ -1460,10 +1382,55 @@ describe('given the load domain connections by user id function', () => { 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 + + 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) + }) + }) + 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, + isAffiliated: true, + } + const domains = await connectionLoader({ ...connectionArgs }) + + 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 @@ -1591,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.`), ) } @@ -1683,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([ @@ -1713,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([ @@ -1729,9 +1686,7 @@ 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, @@ -1749,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: ${ @@ -1765,9 +1716,7 @@ 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, @@ -1785,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: ${ @@ -1804,11 +1749,7 @@ 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, @@ -1826,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([ @@ -1863,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([ @@ -1939,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é."), ) } @@ -2031,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([ @@ -2061,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([ @@ -2077,9 +2004,7 @@ 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, @@ -2098,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([ @@ -2113,9 +2036,7 @@ 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, @@ -2134,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([ @@ -2152,11 +2071,7 @@ 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, @@ -2174,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([ @@ -2213,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 index 4bb37015e1..a34780f2e2 100644 --- a/api/src/domain/loaders/index.js +++ b/api/src/domain/loaders/index.js @@ -2,3 +2,4 @@ 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/src/domain/loaders/load-domain-by-domain.js b/api/src/domain/loaders/load-domain-by-domain.js index a85cd20020..cd2fe6fe42 100644 --- a/api/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/src/domain/loaders/load-domain-by-key.js b/api/src/domain/loaders/load-domain-by-key.js index 03d08af21c..0d8e9da6c1 100644 --- a/api/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 index 1079c437e9..14dd6d7a55 100644 --- a/api/src/domain/loaders/load-domain-connections-by-organizations-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-organizations-id.js @@ -1,31 +1,11 @@ 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, 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 - ) - ` - } - } - + async ({ orgId, permission, after, before, first, last, ownership, orderBy, search, filters = [] }) => { let afterTemplate = aql`` let afterVar = aql`` @@ -76,6 +56,9 @@ export const loadDomainConnectionsByOrgId = } 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` @@ -136,6 +119,9 @@ export const loadDomainConnectionsByOrgId = } 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` @@ -152,18 +138,14 @@ export const loadDomainConnectionsByOrgId = `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.`, - ), + 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.`, - ), + 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 */ @@ -172,17 +154,11 @@ export const loadDomainConnectionsByOrgId = 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.`, - ), - ) + 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.`, - ) + 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.`, @@ -199,9 +175,7 @@ export const loadDomainConnectionsByOrgId = 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}\`.`), - ) + 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)` @@ -269,6 +243,10 @@ export const loadDomainConnectionsByOrgId = 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` @@ -306,115 +284,117 @@ export const loadDomainConnectionsByOrgId = 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},` } } - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } + const domainFilters = buildDomainFilters({ filters }) let domainQuery = aql`` - let loopString = aql`FOR domain IN domains` - let totalCount = aql`LENGTH(domainKeys)` + let loopString = aql`FOR domain IN collectedDomains` + let totalCount = aql`LENGTH(collectedDomains)` 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 + 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 domainKeysQuery + let collectedDomains if (!loginRequiredBool) { - domainKeysQuery = aql` - LET domainKeys = ( - FOR v, e IN 1..1 OUTBOUND ${orgId} claims - OPTIONS {bfs: true} - RETURN v._key + 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 { - domainKeysQuery = aql` - 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 - ))` + collectedDomains = aql` + LET collectedDomains = UNIQUE( + ${ownershipOrgsOnly} + )` } requestedDomainInfo = await query` - WITH affiliations, domains, organizations, users - - ${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 - } - ` + 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}`, 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 index 120abd3cdc..b15b39d901 100644 --- a/api/src/domain/loaders/load-domain-connections-by-user-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-user-id.js @@ -1,6 +1,7 @@ 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 } }) => @@ -12,25 +13,20 @@ export const loadDomainConnectionsByUserId = ownership, orderBy, isSuperAdmin, + myTracker, search, + isAffiliated, + filters = [], }) => { const userDBId = `users/${userKey}` let ownershipOrgsOnly = aql` - LET claimDomainKeys = ( - FOR v, e IN 1..1 OUTBOUND orgId claims - OPTIONS {bfs: true} - RETURN v._key - ) + FOR v, e IN 1..1 OUTBOUND org._id claims ` 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 - ) + FOR v, e IN 1..1 OUTBOUND org._id ownership ` } } @@ -85,6 +81,9 @@ export const loadDomainConnectionsByUserId = } 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` @@ -145,6 +144,9 @@ export const loadDomainConnectionsByUserId = } 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` @@ -161,18 +163,14 @@ export const loadDomainConnectionsByUserId = `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.`, - ), + 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.`, - ), + 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 */ @@ -181,11 +179,7 @@ export const loadDomainConnectionsByUserId = 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.`, - ), - ) + 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 @@ -208,9 +202,7 @@ export const loadDomainConnectionsByUserId = 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}\`.`), - ) + 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)` @@ -278,6 +270,10 @@ export const loadDomainConnectionsByUserId = 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` @@ -315,77 +311,100 @@ export const loadDomainConnectionsByUserId = 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},` } } - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } + const domainFilters = buildDomainFilters({ filters }) let domainKeysQuery - if (!loginRequiredBool) { + 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 domainKeys = UNIQUE(FLATTEN( - LET keys = [] - LET orgIds = (FOR org IN organizations RETURN org._id) - FOR orgId IN orgIds - LET claimDomainKeys = ( - FOR v, e IN 1..1 OUTBOUND orgId claims - OPTIONS {bfs: true} - RETURN v._key - ) - RETURN APPEND(keys, claimDomainKeys) - )) + 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 (isSuperAdmin) { + } else if (!loginRequiredBool) { 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) - )) + 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 domainKeys = UNIQUE(FLATTEN( - LET keys = [] - LET orgIds = ( - FOR v, e IN 1..1 ANY ${userDBId} affiliations - OPTIONS {bfs: true} - RETURN e._from + LET collectedDomains = UNIQUE( + LET userAffiliations = ( + FOR v, e IN 1..1 INBOUND ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v ) - FOR orgId IN orgIds - ${ownershipOrgsOnly} - RETURN APPEND(keys, claimDomainKeys) - )) - + 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 domains` - let totalCount = aql`LENGTH(domainKeys)` + let loopString = aql`FOR domain IN collectedDomains` + let totalCount = aql`LENGTH(collectedDomains)` 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 + 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)` } @@ -402,7 +421,6 @@ export const loadDomainConnectionsByUserId = LET retrievedDomains = ( ${loopString} - FILTER domain._key IN domainKeys ${afterTemplate} ${beforeTemplate} SORT @@ -413,17 +431,15 @@ export const loadDomainConnectionsByUserId = LET hasNextPage = (LENGTH( ${loopString} - FILTER domain._key IN domainKeys ${hasNextPageFilter} - SORT ${sortByField} TO_NUMBER(domain._key) ${sortString} LIMIT 1 + 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 + LIMIT 1 RETURN domain ) > 0 ? true : false) 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 index 9633610dda..01cc7ef874 100644 --- a/api/src/domain/mutations/__tests__/create-domain.test.js +++ b/api/src/domain/mutations/__tests__/create-domain.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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -15,57 +16,39 @@ import { saltedHash, verifiedRequired, tfaRequired, + checkDomainPermission, + AuthDataSource, } from '../../../auth' -import { loadDomainByDomain } from '../../loaders' -import { - loadOrgByKey, - loadOrgConnectionsByDomainId, -} from '../../../organization/loaders' +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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('create a domain', () => { - let query, drop, truncate, schema, collections, transaction, user, org + 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 @@ -94,6 +77,24 @@ describe('create a domain', () => { })) }) 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, @@ -140,15 +141,15 @@ describe('create a domain', () => { }) }) it('returns the domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { createDomain( input: { orgId: "${toGlobalId('organization', org._key)}" domain: "test.gc.ca" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -181,8 +182,8 @@ describe('create a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'en', }, @@ -190,7 +191,13 @@ describe('create a domain', () => { 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({ @@ -201,7 +208,27 @@ describe('create a domain', () => { 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({ @@ -215,27 +242,22 @@ describe('create a domain', () => { }, 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', 'selector2'], + lastRan: '2021-01-01 12:00:00.000000', + selectors: [], status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', }, organizations: { edges: [ @@ -293,15 +315,15 @@ describe('create a domain', () => { }) }) it('returns the domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { createDomain( input: { orgId: "${toGlobalId('organization', org._key)}" domain: "test.gc.ca" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -334,8 +356,8 @@ describe('create a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'en', }, @@ -343,7 +365,13 @@ describe('create a domain', () => { 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({ @@ -354,7 +382,27 @@ describe('create a domain', () => { 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({ @@ -368,13 +416,7 @@ describe('create a domain', () => { }, validators: { cleanseInput, slugify }, }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() + }) const expectedResponse = { data: { @@ -382,14 +424,14 @@ describe('create a domain', () => { result: { id: toGlobalId('domain', domain._key), domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1', 'selector2'], + lastRan: '2021-01-01 12:00:00.000000', + selectors: [], status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', }, organizations: { edges: [ @@ -423,15 +465,15 @@ describe('create a domain', () => { }) }) it('returns the domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { createDomain( input: { orgId: "${toGlobalId('organization', org._key)}" domain: "test.gc.ca" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -464,8 +506,8 @@ describe('create a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'en', }, @@ -473,7 +515,13 @@ describe('create a domain', () => { 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({ @@ -484,7 +532,27 @@ describe('create a domain', () => { 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({ @@ -498,13 +566,7 @@ describe('create a domain', () => { }, validators: { cleanseInput, slugify }, }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() + }) const expectedResponse = { data: { @@ -512,14 +574,14 @@ describe('create a domain', () => { result: { id: toGlobalId('domain', domain._key), domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1', 'selector2'], + lastRan: '2021-01-01 12:00:00.000000', + selectors: [], status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', }, organizations: { edges: [ @@ -543,24 +605,65 @@ describe('create a domain', () => { ]) }) }) - describe('user has user permission level', () => { + 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: org._id, + _from: secondOrg._id, _to: user._id, - permission: 'user', + 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( + const response = await graphql({ schema, - ` + source: ` mutation { createDomain( input: { - orgId: "${toGlobalId('organization', org._key)}" + orgId: "${toGlobalId('organization', secondOrg._key)}" domain: "test.gc.ca" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -593,8 +696,8 @@ describe('create a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'en', }, @@ -602,7 +705,13 @@ describe('create a domain', () => { 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({ @@ -613,7 +722,27 @@ describe('create a domain', () => { 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({ @@ -627,13 +756,7 @@ describe('create a domain', () => { }, validators: { cleanseInput, slugify }, }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() + }) const expectedResponse = { data: { @@ -641,14 +764,14 @@ describe('create a domain', () => { result: { id: toGlobalId('domain', domain._key), domain: 'test.gc.ca', - lastRan: null, + lastRan: '2021-01-01 12:00:00.000000', selectors: ['selector1', 'selector2'], status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', }, organizations: { edges: [ @@ -658,6 +781,12 @@ describe('create a domain', () => { name: 'Treasury Board of Canada Secretariat', }, }, + { + node: { + id: toGlobalId('organization', secondOrg._key), + name: 'Communications Security Establishment', + }, + }, ], }, }, @@ -668,71 +797,26 @@ describe('create a domain', () => { expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, + `User: ${user._key} successfully created ${domain.domain} in org: communications-security-establishment.`, ]) }) - }) - 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', () => { + describe('lastRan is not changed', () => { beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1', 'selector2'], - 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( + const response = await graphql({ schema, - ` + source: ` mutation { createDomain( input: { orgId: "${toGlobalId('organization', secondOrg._key)}" domain: "test.gc.ca" + assetState: APPROVED } ) { result { @@ -765,8 +849,8 @@ describe('create a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'en', }, @@ -774,7 +858,13 @@ describe('create a domain', () => { 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({ @@ -785,7 +875,27 @@ describe('create a domain', () => { 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({ @@ -799,13 +909,7 @@ describe('create a domain', () => { }, validators: { cleanseInput, slugify }, }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() + }) const expectedResponse = { data: { @@ -813,14 +917,14 @@ describe('create a domain', () => { result: { id: toGlobalId('domain', domain._key), domain: 'test.gc.ca', - lastRan: null, + lastRan: '2021-01-01 12:00:00.000000', selectors: ['selector1', 'selector2'], status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', }, organizations: { edges: [ @@ -850,35 +954,23 @@ describe('create a domain', () => { ]) }) }) - describe('selectors are the same', () => { + describe('status do not changed', () => { beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1', 'selector2'], - 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( + const response = await graphql({ schema, - ` + source: ` mutation { createDomain( input: { orgId: "${toGlobalId('organization', secondOrg._key)}" domain: "test.gc.ca" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -911,8 +1003,8 @@ describe('create a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'en', }, @@ -920,7 +1012,13 @@ describe('create a domain', () => { 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({ @@ -931,7 +1029,27 @@ describe('create a domain', () => { 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({ @@ -945,13 +1063,7 @@ describe('create a domain', () => { }, validators: { cleanseInput, slugify }, }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() + }) const expectedResponse = { data: { @@ -959,14 +1071,14 @@ describe('create a domain', () => { result: { id: toGlobalId('domain', domain._key), domain: 'test.gc.ca', - lastRan: null, + lastRan: '2021-01-01 12:00:00.000000', selectors: ['selector1', 'selector2'], status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, + dkim: 'INFO', + dmarc: 'INFO', + https: 'INFO', + spf: 'INFO', + ssl: 'INFO', }, organizations: { edges: [ @@ -996,1564 +1108,32 @@ describe('create a domain', () => { ]) }) }) - describe('new selectors are added', () => { - beforeEach(async () => { - const domain = await collections.domains.save({ - domain: 'test.gc.ca', - selectors: ['selector1', 'selector2'], - 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", "selector4"] - } - ) { - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - 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({}), - }, - loaders: { - 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 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', - 'selector2', - 'selector3', - 'selector4', - ], - 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', 'selector2'], - 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", "selector2"] - } - ) { - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - 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({}), - }, - loaders: { - 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 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', 'selector2'], - 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', 'selector2'], - 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", "selector2"] - } - ) { - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - 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({}), - }, - loaders: { - 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 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', 'selector2'], - 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", "selector2"] - } - ) { - 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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - saltedHash: jest.fn(), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - 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(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - 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(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), - }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - 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(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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", "selector2"] - } - ) { - 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: collectionNames, - 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(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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('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, - ` + }) + 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" - selectors: ["selector1", "selector2"] - } + input: { orgId: "b3JnYW5pemF0aW9uOjE=", domain: "test.gc.ca", assetState: APPROVED, cvdEnrollment: { status: ENROLLED } } ) { result { ... on Domain { @@ -2568,6 +1148,9 @@ describe('create a domain', () => { spf ssl } + cvdEnrollment { + status + } organizations(first: 5) { edges { node { @@ -2585,68 +1168,155 @@ describe('create a domain', () => { } } `, - null, - { - i18n, - request: { - language: 'en', + 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(), }, - query, - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn(), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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(), }, - loaders: { - loadDomainByDomain: { - load: jest.fn(), - }, - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - loadOrgConnectionsByDomainId: jest.fn(), - loadUserByKey: { - load: jest.fn(), - }, + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), }, - validators: { cleanseInput, slugify }, - }, - ) - - const error = { - data: { - createDomain: { - result: { - code: 400, - description: - 'Impossible de créer un domaine dans une organisation inconnue.', - }, + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), }, }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to create a domain to an organization: 1 that does not exist.`, - ]) + 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, - ` + }) + }) + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -2679,70 +1349,82 @@ describe('create a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + publish: jest.fn(), + auth: { + checkDomainPermission: checkDomainPermission({ i18n, - request: { - language: 'en', - }, + userKey: user._key, query, - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue(undefined), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, + }), + 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 refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.", - }, - }, + const error = { + data: { + createDomain: { + result: { + code: 403, + 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, - ` + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -2775,73 +1457,85 @@ describe('create a domain', () => { } } `, - null, - { + 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, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValue({}), + 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', }), - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + 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é.", - }, - }, + 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, - ` + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -2874,68 +1568,75 @@ describe('create a domain', () => { } } `, - null, - { + 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, - request: { - language: 'en', - }, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, + 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( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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.`, - ]) - }) - }) + 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, - ` + }) + }) + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -2968,68 +1669,75 @@ describe('create a domain', () => { } } `, - null, - { + 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, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), + 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', }), - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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, - ` + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -3062,75 +1770,83 @@ describe('create a domain', () => { } } `, - null, - { + 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, - 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')), - }), + 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', }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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`, - ]) - }) - }) + 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, - ` + }) + }) + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -3163,70 +1879,78 @@ describe('create a domain', () => { } } `, - null, - { + 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, - 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')), + 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', }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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, - ` + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -3259,75 +1983,85 @@ describe('create a domain', () => { } } `, - null, - { + 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, - 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')), + 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', }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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`, - ]) - }) - }) + 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, - ` + }) + }) + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -3360,75 +2094,83 @@ describe('create a domain', () => { } } `, - null, - { + 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, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), + 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: '', }), - collections: collectionNames, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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, - ` + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -3461,79 +2203,89 @@ describe('create a domain', () => { } } `, - null, - { + 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, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), + 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: '', }), - collections: collectionNames, - transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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`, - ]) - }) - }) + 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, - ` + }) + }) + 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" - selectors: ["selector1", "selector2"] + assetState: APPROVED } ) { result { @@ -3566,65 +2318,71 @@ describe('create a domain', () => { } } `, - null, - { + 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, - request: { - language: 'en', - }, - query: jest.fn().mockReturnValue({ - next: jest.fn().mockReturnValueOnce(undefined), + 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: '', }), - collections: collectionNames, - transaction: jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), + }, + loadOrgByKey: { + load: jest.fn().mockReturnValue({ + slug: 'treasury-board-secretariat', }), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - saltedHash: jest.fn(), - verifiedRequired: jest.fn(), - tfaRequired: 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 }, }, - ) + loadOrgConnectionsByDomainId: jest.fn(), + loadUserByKey: { + load: jest.fn(), + }, + }, + validators: { cleanseInput, slugify }, + }, + }) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + 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`, - ]) - }) - }) + 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/src/domain/mutations/__tests__/remove-domain.test.js b/api/src/domain/mutations/__tests__/remove-domain.test.js index 2a2425c0b3..339bdeb572 100644 --- a/api/src/domain/mutations/__tests__/remove-domain.test.js +++ b/api/src/domain/mutations/__tests__/remove-domain.test.js @@ -1,5 +1,6 @@ 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' @@ -8,52 +9,15 @@ import { createMutationSchema } from '../../../mutation' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' import { cleanseInput } from '../../../validators' -import { - checkPermission, - userRequired, - verifiedRequired, - tfaRequired, -} 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('removing a domain', () => { let schema, i18n, query, drop, truncate, collections, transaction, user @@ -146,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, }) @@ -240,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)}" } @@ -265,13 +219,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -291,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', }, @@ -308,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.`, ]) }) }) @@ -328,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)}" } @@ -353,13 +309,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -379,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', }, @@ -397,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)}" } @@ -427,8 +384,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -453,7 +410,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -465,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)}" } @@ -490,8 +448,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -516,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 () => { @@ -556,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)}" } @@ -581,13 +523,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -607,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) @@ -621,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) }) }) @@ -634,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)}" } @@ -659,8 +601,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -685,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() @@ -699,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() }) }) @@ -753,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)}" } @@ -778,13 +719,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -804,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', }, @@ -821,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.`, ]) }) }) @@ -841,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)}" } @@ -866,13 +809,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -892,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', }, @@ -910,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)}" } @@ -940,8 +884,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -966,7 +910,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -978,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)}" } @@ -1003,8 +948,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1029,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 () => { @@ -1069,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)}" } @@ -1094,13 +1027,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1120,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) @@ -1134,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) }) }) @@ -1147,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)}" } @@ -1172,8 +1105,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1198,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() @@ -1212,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() }) }) @@ -1243,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)}" } @@ -1268,13 +1200,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1294,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', }, @@ -1311,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.`, ]) }) }) @@ -1331,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)}" } @@ -1356,13 +1290,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1382,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', }, @@ -1400,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)}" } @@ -1430,13 +1365,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1456,7 +1392,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -1467,13 +1403,14 @@ describe('removing a domain', () => { const domainCheck = await domainCursor.next() expect(domainCheck).toEqual(undefined) }) - it.skip('removes all scan data', async () => { - await graphql( + it('removes all scan data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -1493,13 +1430,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1519,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 () => { @@ -1595,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)}" } @@ -1620,13 +1509,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1646,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) @@ -1660,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) }) }) @@ -1673,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)}" } @@ -1698,8 +1587,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1724,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() @@ -1738,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() }) }) @@ -1761,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)}" } @@ -1786,13 +1674,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1812,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', }, @@ -1829,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.`, ]) }) }) @@ -1849,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)}" } @@ -1874,13 +1764,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1900,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', }, @@ -1918,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)}" } @@ -1948,13 +1839,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1974,7 +1866,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -1985,13 +1877,14 @@ describe('removing a domain', () => { const domainCheck = await domainCursor.next() expect(domainCheck).toEqual(undefined) }) - it.skip('removes all scan data', async () => { - await graphql( + it('removes all scan data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -2011,13 +1904,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2037,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 - ` + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(undefined) - 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) + 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 () => { @@ -2113,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)}" } @@ -2138,13 +1983,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2164,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) @@ -2178,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) }) }) @@ -2191,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)}" } @@ -2216,8 +2061,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2242,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() @@ -2256,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() }) }) @@ -2300,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, @@ -2395,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)}" } @@ -2420,13 +2253,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2446,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', }, @@ -2463,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.`, ]) }) }) @@ -2483,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)}" } @@ -2508,13 +2343,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2534,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', }, @@ -2552,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)}" } @@ -2582,8 +2418,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2608,7 +2444,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -2620,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)}" } @@ -2645,8 +2482,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2671,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 () => { @@ -2711,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)}" } @@ -2736,13 +2557,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2762,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) @@ -2776,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) }) }) @@ -2789,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)}" } @@ -2814,8 +2635,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2840,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() @@ -2854,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() }) }) @@ -2879,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)}" } @@ -2904,13 +2724,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2930,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', }, @@ -2947,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.`, ]) }) }) @@ -2967,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)}" } @@ -2992,13 +2814,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -3018,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', }, @@ -3036,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)}" } @@ -3066,13 +2889,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -3092,7 +2916,7 @@ describe('removing a domain', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const domainCursor = await query` FOR domain IN domains @@ -3103,13 +2927,14 @@ describe('removing a domain', () => { const domainCheck = await domainCursor.next() expect(domainCheck).toEqual(undefined) }) - it.skip('removes all scan data', async () => { - await graphql( + it('removes all scan data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeDomain( input: { + reason: WRONG_ORG, domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" } @@ -3129,13 +2954,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -3155,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 () => { @@ -3231,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)}" } @@ -3256,13 +3029,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -3282,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) @@ -3296,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) }) }) @@ -3309,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)}" } @@ -3334,8 +3107,8 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -3360,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() @@ -3374,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() }) }) @@ -3401,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)}" } @@ -3426,13 +3198,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -3448,7 +3221,7 @@ describe('removing a domain', () => { loadUserByKey: jest.fn(), }, }, - ) + }) const error = { data: { @@ -3469,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)}" } @@ -3494,13 +3268,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -3518,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.', }, }, }, @@ -3541,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)}" } @@ -3566,13 +3341,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -3583,7 +3359,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3594,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.', }, }, }, @@ -3610,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)}" } @@ -3641,13 +3417,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -3658,7 +3435,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3669,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.', }, }, }, @@ -3685,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)}" } @@ -3716,13 +3493,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), @@ -3733,7 +3511,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3744,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.', }, }, }, @@ -3760,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.`, ]) }) }) @@ -3768,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)}" } @@ -3793,13 +3571,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -3810,7 +3589,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3821,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.', }, }, }, @@ -3837,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)}" } @@ -3868,13 +3647,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), @@ -3885,7 +3665,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3896,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.', }, }, }, @@ -3912,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.`, ]) }) }) @@ -3920,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)}" } @@ -3945,13 +3725,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('database error')), collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -3962,7 +3743,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -3973,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)}" } @@ -4012,16 +3792,19 @@ 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: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -4032,26 +3815,26 @@ describe('removing a domain', () => { 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`, ]) }) }) @@ -4062,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)}" } @@ -4089,13 +3874,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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(), @@ -4106,44 +3895,43 @@ describe('removing a domain', () => { 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)}" } @@ -4163,13 +3951,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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(), @@ -4180,26 +3972,26 @@ describe('removing a domain', () => { 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`, ]) }) }) @@ -4212,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)}" } @@ -4239,16 +4033,20 @@ 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: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -4259,26 +4057,26 @@ describe('removing a domain', () => { 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.`, ]) }) }) @@ -4294,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)}" } @@ -4324,13 +4122,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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(), @@ -4341,47 +4143,48 @@ describe('removing a domain', () => { 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)}" } @@ -4401,13 +4204,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -4418,26 +4222,25 @@ describe('removing a domain', () => { 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`, ]) }) }) @@ -4447,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)}" } @@ -4477,13 +4280,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 2 }), + 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(), @@ -4494,26 +4301,25 @@ describe('removing a domain', () => { 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.`, ]) }) }) @@ -4535,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)}" } @@ -4560,13 +4367,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -4582,7 +4390,7 @@ describe('removing a domain', () => { loadUserByKey: jest.fn(), }, }, - ) + }) const error = { data: { @@ -4603,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)}" } @@ -4628,13 +4437,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -4652,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.", }, }, }, @@ -4675,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)}" } @@ -4700,13 +4510,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -4717,7 +4528,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4728,7 +4539,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4744,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)}" } @@ -4775,13 +4587,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -4792,7 +4605,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4803,7 +4616,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4811,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.", }, }, }, @@ -4819,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)}" } @@ -4850,13 +4664,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), @@ -4867,7 +4682,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4878,7 +4693,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4886,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.", }, }, }, @@ -4894,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.`, ]) }) }) @@ -4902,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)}" } @@ -4927,13 +4743,14 @@ describe('removing a domain', () => { } } `, - null, - { + 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(), @@ -4944,7 +4761,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -4955,7 +4772,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -4971,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)}" } @@ -5002,13 +4820,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), @@ -5019,7 +4838,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -5030,7 +4849,7 @@ describe('removing a domain', () => { }, }, }, - ) + }) const error = { data: { @@ -5046,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.`, ]) }) }) @@ -5054,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)}" } @@ -5079,13 +4899,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn().mockRejectedValue(new Error('database error')), collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -5096,7 +4917,7 @@ describe('removing a domain', () => { loaders: { loadDomainByKey: { load: jest.fn().mockReturnValue({ - slug: 'domain-gc-ca', + domain: 'domain.gc.ca', }), }, loadOrgByKey: { @@ -5107,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)}" } @@ -5148,16 +4966,19 @@ 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: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -5168,28 +4989,25 @@ describe('removing a domain', () => { 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`, ]) }) }) @@ -5200,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)}" } @@ -5227,13 +5047,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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(), @@ -5244,46 +5068,42 @@ describe('removing a domain', () => { 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)}" } @@ -5303,13 +5123,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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(), @@ -5320,28 +5144,25 @@ describe('removing a domain', () => { 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`, ]) }) }) @@ -5354,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)}" } @@ -5381,16 +5204,20 @@ 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: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -5401,28 +5228,25 @@ describe('removing a domain', () => { 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.`, ]) }) }) @@ -5438,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)}" } @@ -5468,13 +5292,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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(), @@ -5485,28 +5313,25 @@ describe('removing a domain', () => { 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`, ]) }) }) @@ -5514,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)}" } @@ -5547,13 +5375,14 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: mockedQuery, collections: collectionNames, transaction: mockedTransaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), @@ -5564,28 +5393,25 @@ describe('removing a domain', () => { 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`, ]) }) }) @@ -5595,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)}" } @@ -5625,13 +5451,17 @@ describe('removing a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 2 }), + 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(), @@ -5642,28 +5472,25 @@ describe('removing a domain', () => { 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__/request-scan.test.js b/api/src/domain/mutations/__tests__/request-scan.test.js deleted file mode 100644 index f4c380e727..0000000000 --- a/api/src/domain/mutations/__tests__/request-scan.test.js +++ /dev/null @@ -1,1370 +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 { 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' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - 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/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 index b9d33ace7e..f5e26e75ec 100644 --- a/api/src/domain/mutations/__tests__/update-domain.test.js +++ b/api/src/domain/mutations/__tests__/update-domain.test.js @@ -1,5 +1,6 @@ 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' @@ -13,49 +14,19 @@ import { userRequired, verifiedRequired, tfaRequired, + checkDomainPermission, + AuthDataSource, } from '../../../auth' -import { loadDomainByKey } from '../../loaders' +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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('updating a domain', () => { - let query, drop, truncate, schema, collections, transaction, user + let query, drop, truncate, schema, collections, transaction, publish, user const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -77,6 +48,18 @@ describe('updating a domain', () => { 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({ @@ -90,6 +73,7 @@ describe('updating a domain', () => { schema: dbschema, })) + publish = jest.fn() }) beforeEach(async () => { user = await collections.users.save({ @@ -124,11 +108,23 @@ describe('updating a domain', () => { domain = await collections.domains.save({ domain: 'test.gc.ca', lastRan: null, - selectors: ['selector1', 'selector2'], + 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 () => { @@ -147,15 +143,15 @@ describe('updating a domain', () => { }) describe('user updates domain', () => { it('returns updated domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" + assetState: APPROVED } ) { result { @@ -164,91 +160,26 @@ describe('updating a domain', () => { domain lastRan selectors + assetState } } } } `, - null, - { + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, + publish, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ + checkDomainPermission: checkDomainPermission({ + i18n, userKey: user._key, - loadUserByKey: loadUserByKey({ query }), + query, }), - verifiedRequired: verifiedRequired({}), - tfaRequired: tfaRequired({}), - }, - 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', 'selector2'], - }, - }, - }, - } - - 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", - "selector4" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, @@ -257,109 +188,44 @@ describe('updating a domain', () => { verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, 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', 'selector4'], - }, - }, - }, - } - - 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", - "selector4" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, userKey: user._key, - loadUserByKey: loadUserByKey({ query }), + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, }), - verifiedRequired: verifiedRequired({}), - tfaRequired: tfaRequired({}), - }, - 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', + domain: 'test.gc.ca', lastRan: null, - selectors: ['selector3', 'selector4'], + selectors: ['selector1', 'selector2'], + assetState: 'APPROVED', }, }, }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) }) @@ -373,15 +239,15 @@ describe('updating a domain', () => { }) describe('user updates domain', () => { it('returns updated domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" + assetState: APPROVED } ) { result { @@ -390,91 +256,26 @@ describe('updating a domain', () => { domain lastRan selectors + assetState } } } } `, - null, - { + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, + publish, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ + checkDomainPermission: checkDomainPermission({ + i18n, userKey: user._key, - loadUserByKey: loadUserByKey({ query }), + query, }), - verifiedRequired: verifiedRequired({}), - tfaRequired: tfaRequired({}), - }, - 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', 'selector2'], - }, - }, - }, - } - - 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", - "selector4" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, @@ -483,109 +284,44 @@ describe('updating a domain', () => { verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, 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', 'selector4'], - }, - }, - }, - } - - 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", - "selector4" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, userKey: user._key, - loadUserByKey: loadUserByKey({ query }), + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, }), - verifiedRequired: verifiedRequired({}), - tfaRequired: tfaRequired({}), - }, - 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', + domain: 'test.gc.ca', lastRan: null, - selectors: ['selector3', 'selector4'], + selectors: ['selector1', 'selector2'], + assetState: 'APPROVED', }, }, }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) }) @@ -599,15 +335,15 @@ describe('updating a domain', () => { }) describe('user updates domain', () => { it('returns updated domain', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', domain._key)}" orgId: "${toGlobalId('organization', org._key)}" - domain: "test.canada.ca" + assetState: APPROVED } ) { result { @@ -616,91 +352,26 @@ describe('updating a domain', () => { domain lastRan selectors + assetState } } } } `, - null, - { + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, + publish, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ + checkDomainPermission: checkDomainPermission({ + i18n, userKey: user._key, - loadUserByKey: loadUserByKey({ query }), + query, }), - verifiedRequired: verifiedRequired({}), - tfaRequired: tfaRequired({}), - }, - 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', 'selector2'], - }, - }, - }, - } - - 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", - "selector4" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, @@ -709,109 +380,44 @@ describe('updating a domain', () => { verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, 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', 'selector4'], - }, - }, - }, - } - - 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", - "selector4" - ] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - } - } - } - } - `, - null, - { - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkPermission: checkPermission({ userKey: user._key, query }), - userRequired: userRequired({ + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, userKey: user._key, - loadUserByKey: loadUserByKey({ query }), + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, }), - verifiedRequired: verifiedRequired({}), - tfaRequired: tfaRequired({}), - }, - 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', + domain: 'test.gc.ca', lastRan: null, - selectors: ['selector3', 'selector4'], + selectors: ['selector1', 'selector2'], + assetState: 'APPROVED', }, }, }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) }) @@ -832,22 +438,19 @@ describe('updating a domain', () => { fr: frenchMessages.messages, }, }) + publish = jest.fn() }) describe('domain cannot be found', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 1)}" orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -856,6 +459,7 @@ describe('updating a domain', () => { domain lastRan selectors + assetState } ... on DomainError { code @@ -865,13 +469,15 @@ describe('updating a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, + publish, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn(), userRequired: jest.fn(), @@ -883,6 +489,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn(), }, @@ -892,7 +505,7 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) const error = { data: { @@ -913,19 +526,15 @@ describe('updating a domain', () => { }) describe('organization cannot be found', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -934,6 +543,7 @@ describe('updating a domain', () => { domain lastRan selectors + assetState } ... on DomainError { code @@ -943,13 +553,15 @@ describe('updating a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, + publish, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn(), userRequired: jest.fn(), @@ -961,6 +573,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -970,7 +589,7 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) const error = { data: { @@ -991,19 +610,15 @@ describe('updating a domain', () => { }) describe('user does not belong to org', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1021,13 +636,15 @@ describe('updating a domain', () => { } } `, - null, - { + 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(), @@ -1039,6 +656,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1048,7 +672,7 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) const error = { data: { @@ -1070,19 +694,15 @@ describe('updating a domain', () => { }) describe('domain and org do not have any edges', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1100,13 +720,15 @@ describe('updating a domain', () => { } } `, - null, - { + 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(), @@ -1118,6 +740,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1127,15 +756,14 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) const error = { data: { updateDomain: { result: { code: 400, - description: - 'Unable to update domain that does not belong to the given organization.', + description: 'Unable to update domain that does not belong to the given organization.', }, }, }, @@ -1150,19 +778,15 @@ describe('updating a domain', () => { describe('database error occurs', () => { describe('while checking for edge connections', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1180,13 +804,15 @@ describe('updating a domain', () => { } } `, - null, - { + 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(), @@ -1198,6 +824,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1207,11 +840,9 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to update domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1223,19 +854,15 @@ describe('updating a domain', () => { describe('transaction step error occurs', () => { describe('when running domain upsert', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1253,17 +880,17 @@ describe('updating a domain', () => { } } `, - null, - { + 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')), + 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(), @@ -1275,6 +902,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1284,11 +918,9 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to update domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1299,19 +931,15 @@ describe('updating a domain', () => { }) describe('transaction commit error occurs', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1329,18 +957,23 @@ describe('updating a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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')), + 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(), @@ -1352,6 +985,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: 123, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1361,16 +1001,11 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] + 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`, - ]) }) }) }) @@ -1391,19 +1026,15 @@ describe('updating a domain', () => { }) describe('domain cannot be found', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 1)}" orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1421,13 +1052,15 @@ describe('updating a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, + publish, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn(), userRequired: jest.fn(), @@ -1439,6 +1072,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn(), }, @@ -1448,15 +1088,14 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) const error = { data: { updateDomain: { result: { code: 400, - description: - 'Impossible de mettre à jour un domaine inconnu.', + description: 'Impossible de mettre à jour un domaine inconnu.', }, }, }, @@ -1470,19 +1109,15 @@ describe('updating a domain', () => { }) describe('organization cannot be found', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 1)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1500,13 +1135,15 @@ describe('updating a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, + publish, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { checkPermission: jest.fn(), userRequired: jest.fn(), @@ -1518,6 +1155,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1527,15 +1171,14 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) const error = { data: { updateDomain: { result: { code: 400, - description: - 'Impossible de mettre à jour le domaine dans un org inconnu.', + description: 'Impossible de mettre à jour le domaine dans un org inconnu.', }, }, }, @@ -1549,19 +1192,15 @@ describe('updating a domain', () => { }) describe('user does not belong to org', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1579,13 +1218,15 @@ describe('updating a domain', () => { } } `, - null, - { + 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(), @@ -1597,6 +1238,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1606,7 +1254,7 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) const error = { data: { @@ -1628,19 +1276,15 @@ describe('updating a domain', () => { }) describe('domain and org do not have any edges', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1658,13 +1302,15 @@ describe('updating a domain', () => { } } `, - null, - { + 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(), @@ -1676,6 +1322,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1685,15 +1338,14 @@ describe('updating a domain', () => { 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.", + description: "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.", }, }, }, @@ -1708,19 +1360,15 @@ describe('updating a domain', () => { describe('database error occurs', () => { describe('while checking for edge connections', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1738,13 +1386,15 @@ describe('updating a domain', () => { } } `, - null, - { + 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(), @@ -1756,6 +1406,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1765,13 +1422,9 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1783,19 +1436,15 @@ describe('updating a domain', () => { describe('transaction step error occurs', () => { describe('when running domain upsert', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1813,17 +1462,17 @@ describe('updating a domain', () => { } } `, - null, - { + 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')), + 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(), @@ -1835,6 +1484,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: 123, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1844,13 +1500,9 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1861,19 +1513,15 @@ describe('updating a domain', () => { }) describe('transaction commit error occurs', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateDomain ( input: { domainId: "${toGlobalId('domain', 123)}" orgId: "${toGlobalId('organization', 123)}" - domain: "test.canada.ca" - selectors: [ - "selector3", - "selector4" - ] + assetState: APPROVED } ) { result { @@ -1891,18 +1539,23 @@ describe('updating a domain', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: jest.fn().mockReturnValue({ count: 1 }), + 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')), + 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(), @@ -1914,6 +1567,13 @@ describe('updating a domain', () => { slugify, }, loaders: { + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), loadDomainByKey: { load: jest.fn().mockReturnValue({}), }, @@ -1923,18 +1583,11 @@ describe('updating a domain', () => { loadUserByKey: { load: jest.fn() }, }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] + 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/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 index 7751bdd31c..9057117bdc 100644 --- a/api/src/domain/mutations/create-domain.js +++ b/api/src/domain/mutations/create-domain.js @@ -1,33 +1,53 @@ -import { GraphQLNonNull, GraphQLList, GraphQLID } from 'graphql' +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, Selectors } from '../../scalars' +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: GraphQLNonNull(GraphQLID), - description: - 'The global id of the organization you wish to assign this domain to.', + type: new GraphQLNonNull(GraphQLID), + description: 'The global id of the organization you wish to assign this domain to.', }, domain: { - type: GraphQLNonNull(Domain), + type: new 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.', + 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, + }, + highAvailability: { + description: 'Value that determines if the service is scanned for uptime.', + type: GraphQLBoolean, }, }), outputFields: () => ({ result: { type: createDomainUnion, - description: - '`CreateDomainUnion` returning either a `Domain`, or `CreateDomainError` object.', + description: '`CreateDomainUnion` returning either a `Domain`, or `CreateDomainError` object.', resolve: (payload) => payload, }, }), @@ -40,14 +60,9 @@ export const createDomain = new mutationWithClientMutationId({ collections, transaction, userKey, - auth: { - checkPermission, - saltedHash, - userRequired, - verifiedRequired, - tfaRequired, - }, - loaders: { loadDomainByDomain, loadOrgByKey }, + publish, + auth: { checkPermission, saltedHash, userRequired, tfaRequired, verifiedRequired }, + loaders: { loadDomainByDomain, loadOrgByKey, loadTagByTagId }, validators: { cleanseInput }, }, ) => { @@ -61,61 +76,108 @@ export const createDomain = new mutationWithClientMutationId({ 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)) + 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 { - selectors = [] + archived = false } + let assetState + if (typeof args.assetState !== 'undefined') { + assetState = cleanseInput(args.assetState) + } else { + assetState = 'approved' + } + + const cvdEnrollment = args.cvdEnrollment || { status: 'not-enrolled' } + const highAvailability = args.highAvailability || false + // 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.`, - ) + 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.`, - ), + 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: 403, + description: i18n._(t`Permission Denied: Please contact organization user for help with creating domain.`), + } + } + + // ensure only owners can enroll or deny domains if ( - permission !== 'user' && - permission !== 'admin' && - permission !== 'super_admin' + !ac.can(permission).createOwn('cvd-enrollment').granted && + ['enrolled', 'deny'].includes(cvdEnrollment.status) ) { console.warn( - `User: ${userKey} attempted to create a domain in: ${org.slug}, however they do not have permission to do so.`, + `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' + } + + if (!ac.can(permission).createAny('domain').granted && highAvailability === true) { + console.warn( + `User: ${userKey} attempted to create a high availability 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.`, - ), + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with creating domain.`), } } const insertDomain = { domain: domain.toLowerCase(), lastRan: null, - selectors: selectors, hash: saltedHash(domain.toLowerCase()), status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, + certificates: 'info', + ciphers: 'info', + curves: 'info', + dkim: 'info', + dmarc: 'info', + hsts: 'info', + https: 'info', + protocols: 'info', + spf: 'info', + ssl: 'info', }, + archived, + ignoreRua: false, + cvdEnrollment, + highAvailability, } // Check to see if domain already belongs to same org @@ -132,9 +194,7 @@ export const createDomain = new mutationWithClientMutationId({ 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}`, - ) + 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.`)) } @@ -142,9 +202,7 @@ export const createDomain = new mutationWithClientMutationId({ 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}`, - ) + 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.`)) } @@ -155,125 +213,65 @@ export const createDomain = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to create domain, organization has already claimed it.`, - ), + 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) - // 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}`, - ) - 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 + 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 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) - } - }) + ) + } 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.`)) + } - 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.`)) - } + 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, domains, organizations + try { + await trx.step( + () => + query` + WITH claims INSERT { _from: ${org._id}, - _to: ${checkDomain._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 domain edge: ${err}`, - ) - throw new Error(i18n._(t`Unable to create domain. Please try again.`)) - } + ) + } 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}`, - ) + 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.`)) } @@ -281,12 +279,94 @@ export const createDomain = new mutationWithClientMutationId({ await loadDomainByDomain.clear(insertDomain.domain) const returnDomain = await loadDomainByDomain.load(insertDomain.domain) - console.info( - `User: ${userKey} successfully created ${returnDomain.domain} in org: ${org.slug}.`, - ) + 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 index e873b67aff..295cc20cea 100644 --- a/api/src/domain/mutations/index.js +++ b/api/src/domain/mutations/index.js @@ -1,4 +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 index cad09a50f6..bde0ff54af 100644 --- a/api/src/domain/mutations/remove-domain.js +++ b/api/src/domain/mutations/remove-domain.js @@ -3,25 +3,31 @@ 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: GraphQLNonNull(GraphQLID), + type: new GraphQLNonNull(GraphQLID), description: 'The global id of the domain you wish to remove.', }, orgId: { - type: GraphQLNonNull(GraphQLID), + 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: GraphQLNonNull(removeDomainUnion), - description: - '`RemoveDomainUnion` returning either a `DomainResultType`, or `DomainErrorType` object.', + type: new GraphQLNonNull(removeDomainUnion), + description: '`RemoveDomainUnion` returning either a `DomainResultType`, or `DomainErrorType` object.', resolve: (payload) => payload, }, }), @@ -33,6 +39,7 @@ export const removeDomain = new mutationWithClientMutationId({ collections, transaction, userKey, + request: { ip }, auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, validators: { cleanseInput }, loaders: { loadDomainByKey, loadOrgByKey }, @@ -45,9 +52,7 @@ export const removeDomain = new mutationWithClientMutationId({ tfaRequired({ user }) // Cleanse Input - const { type: _domainType, id: domainId } = fromGlobalId( - cleanseInput(args.domainId), - ) + const { type: _domainType, id: domainId } = fromGlobalId(cleanseInput(args.domainId)) const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) // Get domain from db @@ -55,9 +60,7 @@ export const removeDomain = new mutationWithClientMutationId({ // 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.`, - ) + console.warn(`User: ${userKey} attempted to remove ${domainId} however no domain is associated with that id.`) return { _type: 'error', code: 400, @@ -71,44 +74,39 @@ export const removeDomain = new mutationWithClientMutationId({ // 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.`, + `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.`, - ), + 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') { + if (!ac.can(permission).deleteOwn('domain').granted) { 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.`, + `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 super admin for help with removing domain.`, - ), + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing domain.`), } } - if (permission !== 'super_admin' && permission !== 'admin') { + // 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.slug} in ${org.slug} however they do not have permission in that org.`, + `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 organization admin for help with removing domain.`, - ), + description: i18n._(t`Permission Denied: Please contact super admin for help with removing domain.`), } } @@ -117,30 +115,46 @@ export const removeDomain = new mutationWithClientMutationId({ try { countCursor = await query` WITH claims, domains, organizations - FOR v, e IN 1..1 ANY ${domain._id} claims RETURN true + 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.slug}, error: ${err}`, + `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 RETURN true + 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.slug}, error: ${err}`, + `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 Trans action + // Setup Transaction const trx = await transaction(collections) if (dmarcCountCursor.count === 1) { @@ -168,8 +182,9 @@ export const removeDomain = new mutationWithClientMutationId({ ) } 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}`, + `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.`)) } @@ -187,170 +202,110 @@ export const removeDomain = new mutationWithClientMutationId({ ) } catch (err) { console.error( - `Trx step error occurred when removing ownership data for user: ${userKey} while attempting to remove domain: ${domain.slug}, error: ${err}`, + `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 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 - ` - }) - await Promise.all([ - 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 + 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 } - ) - 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 - ` - }), - ]) + ) + 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 scan data for ${domain.slug} in org: ${org.slug}, error: ${err}`, + `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.`)) } - // Remove domain try { + // Remove DNS data 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 + 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 ${domain.slug} in org: ${org.slug}, error: ${err}`, + `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 { @@ -372,8 +327,9 @@ export const removeDomain = new mutationWithClientMutationId({ }) } catch (err) { console.error( - `Trx step error occurred while user: ${userKey} attempted to remove claim for ${domain.slug} in org: ${org.slug}, error: ${err}`, + `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.`)) } } @@ -383,19 +339,38 @@ export const removeDomain = new mutationWithClientMutationId({ 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}`, + `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.slug} from org: ${org.slug}.`, - ) + 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.slug} from ${org.slug}.`, - ), + 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 index 3c49fdf239..c75eff28ce 100644 --- a/api/src/domain/mutations/request-scan.js +++ b/api/src/domain/mutations/request-scan.js @@ -1,20 +1,14 @@ 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 +import { logActivity } from '../../audit-logs' +import { headers } from 'nats' export const requestScan = new mutationWithClientMutationId({ name: 'RequestScan', - description: - 'This mutation is used to step a manual scan on a requested domain.', + description: 'This mutation is used to start a manual scan on a requested domain.', inputFields: () => ({ domain: { type: Domain, @@ -31,31 +25,34 @@ export const requestScan = new mutationWithClientMutationId({ mutateAndGetPayload: async ( args, { + query, + collections, + transaction, i18n, - fetch, userKey, + request: { ip }, + publish, auth: { checkDomainPermission, userRequired, verifiedRequired }, loaders: { loadDomainByDomain }, + dataSources: { webScan }, validators: { cleanseInput }, }, ) => { - // User is required + // Get User const user = await userRequired() - verifiedRequired({ user }) - const requestedDomain = cleanseInput(args.domain) + // Cleanse input + const domainInput = cleanseInput(args.domain) // Check to see if domain exists - const domain = await loadDomainByDomain.load(requestedDomain) + const domain = await loadDomainByDomain.load(domainInput) 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.`), + `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 @@ -63,70 +60,117 @@ export const requestScan = new mutationWithClientMutationId({ 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.`, + `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.`, - ), + 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, - } - + let orgsClaimingDomainQuery try { - await fetch(DNS_SCANNER_ENDPOINT, { - method: 'POST', - body: JSON.stringify(parameters), - }) + 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( - `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.`), + `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 { - await fetch(HTTPS_SCANNER_ENDPOINT, { - method: 'POST', - body: JSON.stringify(parameters), - }) + orgsClaimingDomain = await orgsClaimingDomainQuery.next() } 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.`), + `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 { - await fetch(SSL_SCANNER_ENDPOINT, { - method: 'POST', - body: JSON.stringify(parameters), + 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( - `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.`), + `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}.`, - ) + 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 index 6e368f2aaf..25827e33b3 100644 --- a/api/src/domain/mutations/update-domain.js +++ b/api/src/domain/mutations/update-domain.js @@ -1,39 +1,55 @@ -import { GraphQLID, GraphQLNonNull, GraphQLList } from 'graphql' +import { GraphQLID, GraphQLNonNull, GraphQLList, GraphQLBoolean, GraphQLString } from 'graphql' import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' import { updateDomainUnion } from '../unions' -import { Domain, Selectors } from '../../scalars' +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', + description: 'Mutation allows the modification of domains if domain is updated through out its life-cycle', inputFields: () => ({ domainId: { - type: GraphQLNonNull(GraphQLID), + type: new 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.', + 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, }, - domain: { - type: Domain, - description: 'The new url of the of the old domain.', + ignoreRua: { + description: 'Boolean value that determines if the domain should ignore rua reports.', + type: GraphQLBoolean, }, - selectors: { - type: new GraphQLList(Selectors), + assetState: { + description: 'Value that determines how the domain relates to the organization.', + type: AssetStateEnums, + }, + cvdEnrollment: { description: - 'The updated DKIM selector strings corresponding to this domain.', + 'The Coordinated Vulnerability Disclosure (CVD) enrollment details for this domain, including HackerOne integration status and CVSS requirements.', + type: CvdEnrollmentInputOptions, + }, + highAvailability: { + description: 'Value that determines if the service is scanned for uptime.', + type: GraphQLBoolean, }, }), outputFields: () => ({ result: { type: updateDomainUnion, - description: - '`UpdateDomainUnion` returning either a `Domain`, or `DomainError` object.', + description: '`UpdateDomainUnion` returning either a `Domain`, or `DomainError` object.', resolve: (payload) => payload, }, }), @@ -45,9 +61,10 @@ export const updateDomain = new mutationWithClientMutationId({ collections, transaction, userKey, + request: { ip }, auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, validators: { cleanseInput }, - loaders: { loadDomainByKey, loadOrgByKey }, + loaders: { loadDomainByKey, loadOrgByKey, loadTagByTagId }, }, ) => { // Get User @@ -58,13 +75,43 @@ export const updateDomain = new mutationWithClientMutationId({ 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)) + 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 { - selectors = null + 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 @@ -98,20 +145,14 @@ export const updateDomain = new mutationWithClientMutationId({ // Check permission const permission = await checkPermission({ orgId: org._id }) - if ( - permission !== 'user' && - permission !== 'admin' && - permission !== 'super_admin' - ) { + 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.`, - ), + description: i18n._(t`Permission Denied: Please contact organization user for help with updating this domain.`), } } @@ -138,9 +179,28 @@ export const updateDomain = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to update domain that does not belong to the given organization.`, - ), + 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' + } + + if (!ac.can(permission).updateAny('domain').granted && typeof args.highAvailability !== 'undefined') { + console.warn( + `User: ${userKey} attempted to update a high availability domain in: ${org.slug}, however they do not have permission to do so.`, + ) + return { + _type: 'error', + code: 403, + description: i18n._(t`Permission Denied: Please contact super admin for help with updating domain.`), } } @@ -149,9 +209,10 @@ export const updateDomain = new mutationWithClientMutationId({ // Update domain const domainToInsert = { - domain: updatedDomain.toLowerCase() || domain.domain.toLowerCase(), - lastRan: domain.lastRan, - selectors: selectors || domain.selectors, + archived: typeof archived !== 'undefined' ? archived : domain?.archived, + ignoreRua: typeof args.ignoreRua !== 'undefined' ? args.ignoreRua : domain?.ignoreRua, + cvdEnrollment: typeof cvdEnrollment !== 'undefined' ? cvdEnrollment : domain?.cvdEnrollment, + highAvailability: typeof args.highAvailability !== 'undefined' ? args.highAvailability : domain?.highAvailability, } try { @@ -169,9 +230,53 @@ export const updateDomain = new mutationWithClientMutationId({ 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() @@ -179,6 +284,7 @@ export const updateDomain = new mutationWithClientMutationId({ 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.`)) } @@ -187,8 +293,82 @@ export const updateDomain = new mutationWithClientMutationId({ 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 + 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/src/domain/objects/__tests__/domain-connection.test.js b/api/src/domain/objects/__tests__/domain-connection.test.js index 93732ade59..d999b49398 100644 --- a/api/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/src/domain/objects/__tests__/domain-error.test.js b/api/src/domain/objects/__tests__/domain-error.test.js index bd29ca65aa..dc15c23025 100644 --- a/api/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 index 527a06955b..7a02d582a2 100644 --- a/api/src/domain/objects/__tests__/domain-result.test.js +++ b/api/src/domain/objects/__tests__/domain-result.test.js @@ -1,7 +1,7 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { domainResultType } from '../domain-result' -import { domainType } from '../domain' +import {domainResultType} from '../domain-result' +import {domainType} from '../domain' describe('given the domainResultType object', () => { describe('testing the field definitions', () => { @@ -24,15 +24,15 @@ describe('given the domainResultType object', () => { it('returns the resolved field', () => { const demoType = domainResultType.getFields() - expect(demoType.status.resolve({ status: 'status' })).toEqual('status') + 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' }) + 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 index 07ee54f062..3bff2bd283 100644 --- a/api/src/domain/objects/__tests__/domain-status.test.js +++ b/api/src/domain/objects/__tests__/domain-status.test.js @@ -1,5 +1,5 @@ -import { StatusEnum } from '../../../enums' -import { domainStatus } from '../domain-status' +import {StatusEnum} from '../../../enums' +import {domainStatus} from '../domain-status' describe('given the domainStatus object', () => { describe('testing its field definitions', () => { @@ -70,42 +70,42 @@ describe('given the domainStatus object', () => { it('returns the resolved value', () => { const demoType = domainStatus.getFields() - expect(demoType.ciphers.resolve({ ciphers: 'pass' })).toEqual('pass') + 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') + 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') + 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') + 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') + 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') + expect(demoType.hsts.resolve({hsts: 'pass'})).toEqual('pass') }) }) @@ -179,7 +179,7 @@ describe('given the domainStatus object', () => { it('returns the resolved value', () => { const demoType = domainStatus.getFields() - expect(demoType.protocols.resolve({ protocols: 'pass' })).toEqual( + expect(demoType.protocols.resolve({protocols: 'pass'})).toEqual( 'pass', ) }) @@ -188,14 +188,14 @@ describe('given the domainStatus object', () => { it('returns the resolved value', () => { const demoType = domainStatus.getFields() - expect(demoType.spf.resolve({ spf: 'pass' })).toEqual('pass') + 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') + 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 index 76bfe5274c..b36bc258dd 100644 --- a/api/src/domain/objects/__tests__/domain.test.js +++ b/api/src/domain/objects/__tests__/domain.test.js @@ -1,23 +1,18 @@ -import { - GraphQLNonNull, - GraphQLID, - GraphQLList, - GraphQLString, - GraphQLBoolean, -} from 'graphql' +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 { organizationConnection } from '../../../organization' import { domainStatus } from '../domain-status' -import { dmarcSummaryType } from '../../../dmarc-summaries/objects' -import { emailScanType } from '../../../email-scan/objects' -import { webScanType } from '../../../web-scan/objects' +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', () => { @@ -25,7 +20,7 @@ describe('given the domain object', () => { const demoType = domainType.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 = domainType.getFields() @@ -37,7 +32,7 @@ describe('given the domain object', () => { const demoType = domainType.getFields() expect(demoType).toHaveProperty('dmarcPhase') - expect(demoType.dmarcPhase.type).toMatchObject(GraphQLString) + expect(demoType.dmarcPhase.type).toMatchObject(DmarcPhaseEnum) }) it('has a hasDMARCReport field', () => { const demoType = domainType.getFields() @@ -55,7 +50,7 @@ describe('given the domain object', () => { const demoType = domainType.getFields() expect(demoType).toHaveProperty('selectors') - expect(demoType.selectors.type).toMatchObject(GraphQLList(Selectors)) + expect(demoType.selectors.type).toMatchObject(new GraphQLList(Selectors)) }) it('has a status field', () => { const demoType = domainType.getFields() @@ -67,21 +62,19 @@ describe('given the domain object', () => { const demoType = domainType.getFields() expect(demoType).toHaveProperty('organizations') - expect(demoType.organizations.type).toMatchObject( - organizationConnection.connectionType, - ) + expect(demoType.organizations.type).toMatchObject(organizationConnection.connectionType) }) - it('has an email field', () => { + it('has an dnsScan field', () => { const demoType = domainType.getFields() - expect(demoType).toHaveProperty('email') - expect(demoType.email.type).toMatchObject(emailScanType) + 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(webScanType) + expect(demoType.web.type).toMatchObject(webConnection.connectionType) }) it('has a dmarcSummaryByPeriod field', () => { const demoType = domainType.getFields() @@ -93,9 +86,11 @@ describe('given the domain object', () => { const demoType = domainType.getFields() expect(demoType).toHaveProperty('yearlyDmarcSummaries') - expect(demoType.yearlyDmarcSummaries.type).toMatchObject( - GraphQLList(dmarcSummaryType), - ) + 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', () => { @@ -109,31 +104,56 @@ describe('given the domain object', () => { 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), - ) + 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', - ) + 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') + expect(demoType.dmarcPhase.resolve({ phase: 'not implemented' })).toEqual('not implemented') }) }) describe('testing the hasDMARCReport resolver', () => { @@ -184,9 +204,7 @@ describe('given the domain object', () => { 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') + expect(demoType.lastRan.resolve({ lastRan: '2020-10-02T12:43:39Z' })).toEqual('2020-10-02T12:43:39Z') }) }) describe('testing the selectors resolver', () => { @@ -195,10 +213,23 @@ describe('given the domain object', () => { const selectors = ['selector1', 'selector2'] - expect(demoType.selectors.resolve({ selectors })).toEqual([ - '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', () => { @@ -270,15 +301,15 @@ describe('given the domain object', () => { }, } - expect( + await expect( demoType.organizations.resolve( { _id: '1' }, { first: 1 }, { - loaders: { - loadOrgConnectionsByDomainId: jest - .fn() - .mockReturnValue(expectedResult), + dataSources: { + organization: { + connectionsByDomainId: jest.fn().mockReturnValue(expectedResult), + }, }, auth: { checkSuperAdmin: jest.fn().mockReturnValue(false), @@ -288,21 +319,49 @@ describe('given the domain object', () => { ).resolves.toEqual(expectedResult) }) }) - describe('testing the email resolver', () => { - it('returns the resolved value', () => { + describe('testing the web resolver', () => { + it('returns the resolved value', async () => { const demoType = domainType.getFields() - expect(demoType.email.resolve({ _id: '1', _key: '1' })).toEqual({ + 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 web resolver', () => { - it('returns the resolved value', () => { + describe('testing the DNS resolver', () => { + it('returns the resolved value', async () => { const demoType = domainType.getFields() - expect(demoType.web.resolve({ _id: '1', _key: '1' })).toEqual({ + 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', }) @@ -330,14 +389,10 @@ describe('given the domain object', () => { { userKey: '1', loaders: { - loadDmarcSummaryEdgeByDomainIdAndPeriod: jest - .fn() - .mockReturnValue({ - _to: 'dmarcSummaries/1', - }), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), + loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn().mockReturnValue({ + _to: 'dmarcSummaries/1', + }), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), }, auth: { checkDomainOwnership: jest.fn().mockReturnValue(true), @@ -387,9 +442,7 @@ describe('given the domain object', () => { userKey: '1', loaders: { loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), }, auth: { checkDomainOwnership: jest.fn().mockReturnValue(false), @@ -398,11 +451,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Unable to retrieve DMARC report information for: test1.gc.ca', - ), - ) + ).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.`, @@ -444,9 +493,7 @@ describe('given the domain object', () => { userKey: '1', loaders: { loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), }, auth: { checkDomainOwnership: jest.fn().mockReturnValue(false), @@ -455,11 +502,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca', - ), - ) + ).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.`, @@ -554,11 +597,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Unable to retrieve DMARC report information for: test1.gc.ca', - ), - ) + ).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.`, ]) @@ -607,11 +646,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca', - ), - ) + ).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 index aa6790856a..851b5b32a7 100644 --- a/api/src/domain/objects/domain-connection.js +++ b/api/src/domain/objects/domain-connection.js @@ -1,7 +1,7 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' +import {GraphQLInt} from 'graphql' +import {connectionDefinitions} from 'graphql-relay' -import { domainType } from './domain' +import {domainType} from './domain' export const domainConnection = connectionDefinitions({ name: 'Domain', @@ -10,7 +10,7 @@ export const domainConnection = connectionDefinitions({ totalCount: { type: GraphQLInt, description: 'The total amount of domains the user has access to.', - resolve: ({ totalCount }) => totalCount, + resolve: ({totalCount}) => totalCount, }, }), }) diff --git a/api/src/domain/objects/domain-error.js b/api/src/domain/objects/domain-error.js index 4d625fa303..b4e66613e2 100644 --- a/api/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 index 72eebb2e09..9791e43cf3 100644 --- a/api/src/domain/objects/domain-result.js +++ b/api/src/domain/objects/domain-result.js @@ -1,21 +1,21 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' -import { domainType } from './domain' +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.', + '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 removal was successful.', - resolve: ({ status }) => status, + 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, + resolve: ({domain}) => domain, }, }), }) diff --git a/api/src/domain/objects/domain-status.js b/api/src/domain/objects/domain-status.js index 98b64b421a..948efc31b8 100644 --- a/api/src/domain/objects/domain-status.js +++ b/api/src/domain/objects/domain-status.js @@ -6,6 +6,11 @@ export const domainStatus = new GraphQLObjectType({ 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', @@ -40,9 +45,7 @@ export const domainStatus = new GraphQLObjectType({ type: StatusEnum, description: 'Policy Status', resolve: ({ ciphers, https, hsts, protocols, ssl }) => - [ciphers, https, hsts, protocols, ssl].every((t) => t !== 'fail') - ? 'pass' - : 'fail', + [ciphers, https, hsts, protocols, ssl].every((t) => t !== 'fail') ? 'pass' : 'fail', }, protocols: { type: StatusEnum, diff --git a/api/src/domain/objects/domain.js b/api/src/domain/objects/domain.js index 66d6610133..71537efc41 100644 --- a/api/src/domain/objects/domain.js +++ b/api/src/domain/objects/domain.js @@ -1,22 +1,22 @@ import { t } from '@lingui/macro' -import { - GraphQLBoolean, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString, -} from 'graphql' +import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql' import { connectionArgs, globalIdField } from 'graphql-relay' import { domainStatus } from './domain-status' -import { PeriodEnums } from '../../enums' +import { AssetStateEnums, PeriodEnums, DmarcPhaseEnum } from '../../enums' import { nodeInterface } from '../../node' -import { Domain, Selectors, Year } from '../../scalars' +import { CveID, Domain, Selectors, Year } from '../../scalars' import { dmarcSummaryType } from '../../dmarc-summaries/objects' -import { emailScanType } from '../../email-scan/objects' -import { webScanType } from '../../web-scan/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', @@ -28,24 +28,17 @@ export const domainType = new GraphQLObjectType({ resolve: ({ domain }) => domain, }, dmarcPhase: { - type: GraphQLString, + 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, userRequired, loginRequiredBool } }, - ) => { - if (loginRequiredBool) await userRequired() - const hasDMARCReport = await checkDomainOwnership({ + resolve: async ({ _id }, _, { auth: { checkDomainOwnership } }) => { + return await checkDomainOwnership({ domainId: _id, }) - - return hasDMARCReport }, }, lastRan: { @@ -53,17 +46,60 @@ export const domainType = new GraphQLObjectType({ 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: ({ selectors }) => 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: { @@ -77,58 +113,141 @@ export const domainType = new GraphQLObjectType({ }, isAdmin: { type: GraphQLBoolean, - description: - 'Filter orgs based off of the user being an admin of them.', + 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.', + 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 }, - }, - ) => { + resolve: async ({ _id }, args, { auth: { checkSuperAdmin }, dataSources: { organization } }) => { const isSuperAdmin = await checkSuperAdmin() - const orgs = await loadOrgConnectionsByDomainId({ + return await organization.connectionsByDomainId({ domainId: _id, isSuperAdmin, ...args, }) - return orgs }, }, - email: { - type: emailScanType, - description: 'DKIM, DMARC, and SPF scan results.', - resolve: ({ _id, _key }) => { - return { _id, _key } + 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: webScanType, - description: 'HTTPS, and SSL scan results.', - resolve: ({ _id, _key }) => { - return { _id, _key } + 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: 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.', }, }, @@ -139,29 +258,20 @@ export const domainType = new GraphQLObjectType({ { i18n, userKey, - loaders: { - loadDmarcSummaryEdgeByDomainIdAndPeriod, - loadStartDateFromPeriod, - }, - auth: { checkDomainOwnership, userRequired, loginRequiredBool }, + loaders: { loadDmarcSummaryEdgeByDomainIdAndPeriod, loadStartDateFromPeriod }, + auth: { checkDomainOwnership, userRequired }, }, ) => { - if (loginRequiredBool) { - 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}`, - ), - ) - } + 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 }) @@ -174,7 +284,7 @@ export const domainType = new GraphQLObjectType({ return { domainKey: _key, _id: dmarcSummaryEdge._to, - startDate: startDate, + startDate, } }, }, @@ -184,43 +294,94 @@ export const domainType = new GraphQLObjectType({ resolve: async ( { _id, _key, domain }, __, - { - i18n, - userKey, - loaders: { loadDmarcYearlySumEdge }, - auth: { checkDomainOwnership, userRequired, loginRequiredBool }, - }, + { i18n, userKey, loaders: { loadDmarcYearlySumEdge }, auth: { checkDomainOwnership, userRequired } }, ) => { - if (loginRequiredBool) { - 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}`, - ), - ) - } + 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) => ({ + 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 edges + return cvdEnrollment + }, + }, + highAvailability: { + type: GraphQLBoolean, + description: 'Value that determines if the service is scanned for uptime.', + resolve: async ({ highAvailability }, __, { auth: { checkSuperAdmin } }) => { + const isSuperAdmin = await checkSuperAdmin() + if (isSuperAdmin) return highAvailability + return false }, }, }), diff --git a/api/src/domain/objects/index.js b/api/src/domain/objects/index.js index fab5076f95..a5d96fad94 100644 --- a/api/src/domain/objects/index.js +++ b/api/src/domain/objects/index.js @@ -3,3 +3,4 @@ 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/src/domain/queries/__tests__/find-domain-by-domain.test.js b/api/src/domain/queries/__tests__/find-domain-by-domain.test.js index f08d040952..dea6da436b 100644 --- a/api/src/domain/queries/__tests__/find-domain-by-domain.test.js +++ b/api/src/domain/queries/__tests__/find-domain-by-domain.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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -8,12 +9,8 @@ import frenchMessages from '../../../locale/fr/messages' 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' @@ -43,16 +40,16 @@ describe('given findDomainByDomain query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ @@ -86,7 +83,6 @@ describe('given findDomainByDomain query', () => { domain = await collections.domains.save({ domain: 'test.gc.ca', lastRan: null, - selectors: ['selector1', 'selector2'], status: { dkim: 'pass', dmarc: 'pass', @@ -95,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, @@ -113,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 @@ -132,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, @@ -148,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: { @@ -176,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}.`]) }) }) }) @@ -201,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 @@ -220,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({ @@ -241,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.`]) @@ -253,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 @@ -272,8 +285,8 @@ describe('given findDomainByDomain query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: '1', query: jest.fn(), @@ -294,7 +307,7 @@ describe('given findDomainByDomain query', () => { }, }, }, - ) + }) const error = [ new GraphQLError( @@ -325,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 @@ -337,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({ @@ -358,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.`]) @@ -370,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 @@ -389,8 +400,8 @@ describe('given findDomainByDomain query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: '1', query: jest.fn(), @@ -411,7 +422,7 @@ describe('given findDomainByDomain query', () => { }, }, }, - ) + }) const error = [ new GraphQLError( diff --git a/api/src/domain/queries/__tests__/find-my-domains.test.js b/api/src/domain/queries/__tests__/find-my-domains.test.js index d6b51d54aa..db218facd9 100644 --- a/api/src/domain/queries/__tests__/find-my-domains.test.js +++ b/api/src/domain/queries/__tests__/find-my-domains.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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -8,8 +9,8 @@ import frenchMessages from '../../../locale/fr/messages' 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' @@ -40,22 +41,21 @@ describe('given findMyDomainsQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'french', emailValidated: true, }) @@ -91,7 +91,6 @@ describe('given findMyDomainsQuery', () => { domainOne = await collections.domains.save({ domain: 'test1.gc.ca', lastRan: null, - selectors: ['selector1', 'selector2'], status: { dkim: 'pass', dmarc: 'pass', @@ -103,7 +102,6 @@ describe('given findMyDomainsQuery', () => { domainTwo = await collections.domains.save({ domain: 'test2.gc.ca', lastRan: null, - selectors: ['selector1', 'selector2'], status: { dkim: 'pass', dmarc: 'pass', @@ -112,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, @@ -129,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 { @@ -153,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, @@ -174,6 +195,9 @@ describe('given findMyDomainsQuery', () => { }), verifiedRequired: verifiedRequired({}), }, + dataSources: { + auth: new AuthDataSource({ query, userKey: user._key }), + }, loaders: { loadDomainConnectionsByUserId: loadDomainConnectionsByUserId({ query, @@ -181,9 +205,16 @@ describe('given findMyDomainsQuery', () => { cleanseInput, auth: { loginRequired: true }, }), + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ + query, + userKey: user._key, + cleanseInput, + i18n, + auth: { loginRequiredBool: true }, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -219,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.`]) }) }) }) @@ -243,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 { @@ -271,8 +298,8 @@ describe('given findMyDomainsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 1, auth: { @@ -290,11 +317,9 @@ describe('given findMyDomainsQuery', () => { }), }, }, - ) + }) - 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([ @@ -322,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 { @@ -350,8 +373,8 @@ describe('given findMyDomainsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 1, auth: { @@ -369,13 +392,9 @@ describe('given findMyDomainsQuery', () => { }), }, }, - ) + }) - 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 index a057daf11b..ee896ae55e 100644 --- a/api/src/domain/queries/find-domain-by-domain.js +++ b/api/src/domain/queries/find-domain-by-domain.js @@ -9,7 +9,7 @@ export const findDomainByDomain = { description: 'Retrieve a specific domain by providing a domain.', args: { domain: { - type: GraphQLNonNull(Domain), + type: new GraphQLNonNull(Domain), description: 'The domain you wish to retrieve information for.', }, }, @@ -19,12 +19,7 @@ export const findDomainByDomain = { { i18n, userKey, - auth: { - checkDomainPermission, - userRequired, - verifiedRequired, - loginRequiredBool, - }, + auth: { checkDomainPermission, userRequired, verifiedRequired, loginRequiredBool }, loaders: { loadDomainByDomain }, validators: { cleanseInput }, }, @@ -53,16 +48,12 @@ export const findDomainByDomain = { 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.`, - ), + i18n._(t`Permission Denied: Please contact organization user for help with retrieving this domain.`), ) } } - console.info( - `User ${userKey} successfully retrieved domain ${domain._key}.`, - ) + 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 index ce3e96e6a2..870013d1de 100644 --- a/api/src/domain/queries/find-my-domains.js +++ b/api/src/domain/queries/find-my-domains.js @@ -1,7 +1,7 @@ -import { GraphQLBoolean, GraphQLString } from 'graphql' +import { GraphQLBoolean, GraphQLString, GraphQLList } from 'graphql' import { connectionArgs } from 'graphql-relay' -import { domainOrder } from '../inputs' +import { domainFilter, domainOrder } from '../inputs' import { domainConnection } from '../objects' export const findMyDomains = { @@ -14,13 +14,20 @@ export const findMyDomains = { }, ownership: { type: GraphQLBoolean, - description: - 'Limit domains to those that belong to an organization that has ownership.', + 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 ( @@ -28,12 +35,7 @@ export const findMyDomains = { args, { userKey, - auth: { - checkSuperAdmin, - userRequired, - loginRequiredBool, - verifiedRequired, - }, + auth: { checkSuperAdmin, userRequired, loginRequiredBool, verifiedRequired }, loaders: { loadDomainConnectionsByUserId }, }, ) => { diff --git a/api/src/domain/unions/__tests__/create-domain-union.test.js b/api/src/domain/unions/__tests__/create-domain-union.test.js index 32d2a49e94..11edf2c451 100644 --- a/api/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/src/domain/unions/__tests__/remove-domain-union.test.js b/api/src/domain/unions/__tests__/remove-domain-union.test.js index ba55a11f40..f3ae3326a4 100644 --- a/api/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/src/domain/unions/__tests__/update-domain-union.test.js b/api/src/domain/unions/__tests__/update-domain-union.test.js index 97ec8b3fe3..22d73751c1 100644 --- a/api/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/src/domain/unions/create-domain-union.js b/api/src/domain/unions/create-domain-union.js index 72147aaf40..f19fdbfbc9 100644 --- a/api/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 index 79289f6b1d..d5400b7efc 100644 --- a/api/src/domain/unions/index.js +++ b/api/src/domain/unions/index.js @@ -1,3 +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/src/domain/unions/remove-domain-union.js b/api/src/domain/unions/remove-domain-union.js index ca914fd234..1971fd6f56 100644 --- a/api/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/src/domain/unions/update-domain-union.js b/api/src/domain/unions/update-domain-union.js index 3843656416..8a8b9a274e 100644 --- a/api/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/src/email-scan/index.js b/api/src/email-scan/index.js deleted file mode 100644 index 20b59a8da4..0000000000 --- a/api/src/email-scan/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './loaders' -export * from './objects' -export * from './subscriptions' diff --git a/api/src/email-scan/inputs/__tests__/dkim-order.test.js b/api/src/email-scan/inputs/__tests__/dkim-order.test.js deleted file mode 100644 index e14e8e01b8..0000000000 --- a/api/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/src/email-scan/inputs/__tests__/dkim-result-order.test.js b/api/src/email-scan/inputs/__tests__/dkim-result-order.test.js deleted file mode 100644 index 283c77c247..0000000000 --- a/api/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/src/email-scan/inputs/__tests__/dmarc-order.test.js b/api/src/email-scan/inputs/__tests__/dmarc-order.test.js deleted file mode 100644 index e086a4b897..0000000000 --- a/api/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/src/email-scan/inputs/__tests__/spf-order.test.js b/api/src/email-scan/inputs/__tests__/spf-order.test.js deleted file mode 100644 index 2b53822ef1..0000000000 --- a/api/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/src/email-scan/inputs/dkim-order.js b/api/src/email-scan/inputs/dkim-order.js deleted file mode 100644 index f2d0438bd1..0000000000 --- a/api/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/src/email-scan/inputs/dkim-result-order.js b/api/src/email-scan/inputs/dkim-result-order.js deleted file mode 100644 index 1ec8f62c75..0000000000 --- a/api/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/src/email-scan/inputs/dmarc-order.js b/api/src/email-scan/inputs/dmarc-order.js deleted file mode 100644 index a7ed915d27..0000000000 --- a/api/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/src/email-scan/inputs/index.js b/api/src/email-scan/inputs/index.js deleted file mode 100644 index a8cb1f599e..0000000000 --- a/api/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/src/email-scan/inputs/spf-order.js b/api/src/email-scan/inputs/spf-order.js deleted file mode 100644 index c0b2b1a12b..0000000000 --- a/api/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/src/email-scan/loaders/__tests__/load-dkim-by-key.test.js b/api/src/email-scan/loaders/__tests__/load-dkim-by-key.test.js deleted file mode 100644 index 0f9b2b5233..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-dkim-by-key.test.js +++ /dev/null @@ -1,219 +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 { loadDkimByKey } from '../load-dkim-by-key' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/loaders/__tests__/load-dkim-connections-by-domain-id.test.js b/api/src/email-scan/loaders/__tests__/load-dkim-connections-by-domain-id.test.js deleted file mode 100644 index 6d56ab7770..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-dkim-connections-by-domain-id.test.js +++ /dev/null @@ -1,1334 +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 { cleanseInput } from '../../../validators' -import { loadDkimConnectionsByDomainId, loadDkimByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - 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/src/email-scan/loaders/__tests__/load-dkim-result-by-key.test.js b/api/src/email-scan/loaders/__tests__/load-dkim-result-by-key.test.js deleted file mode 100644 index 5c3588bba1..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-dkim-result-by-key.test.js +++ /dev/null @@ -1,220 +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 { loadDkimResultByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/loaders/__tests__/load-dkim-results-connections-by-dkim-id.test.js b/api/src/email-scan/loaders/__tests__/load-dkim-results-connections-by-dkim-id.test.js deleted file mode 100644 index c43ee706f9..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-dkim-results-connections-by-dkim-id.test.js +++ /dev/null @@ -1,1366 +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 { cleanseInput } from '../../../validators' -import { - loadDkimResultConnectionsByDkimId, - loadDkimResultByKey, -} from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/loaders/__tests__/load-dmarc-by-key.test.js b/api/src/email-scan/loaders/__tests__/load-dmarc-by-key.test.js deleted file mode 100644 index 64acfb40b0..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-dmarc-by-key.test.js +++ /dev/null @@ -1,220 +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 { loadDmarcByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/loaders/__tests__/load-dmarc-connections-by-domain-id.test.js b/api/src/email-scan/loaders/__tests__/load-dmarc-connections-by-domain-id.test.js deleted file mode 100644 index e8a8893d60..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-dmarc-connections-by-domain-id.test.js +++ /dev/null @@ -1,1727 +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 { cleanseInput } from '../../../validators' -import { loadDmarcConnectionsByDomainId, loadDmarcByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - 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/src/email-scan/loaders/__tests__/load-spf-by-key.test.js b/api/src/email-scan/loaders/__tests__/load-spf-by-key.test.js deleted file mode 100644 index bf6b06341b..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-spf-by-key.test.js +++ /dev/null @@ -1,218 +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 { loadSpfByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js b/api/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js deleted file mode 100644 index 6be388e0e3..0000000000 --- a/api/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js +++ /dev/null @@ -1,1617 +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 { cleanseInput } from '../../../validators' -import { loadSpfConnectionsByDomainId, loadSpfByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - 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/src/email-scan/loaders/index.js b/api/src/email-scan/loaders/index.js deleted file mode 100644 index 545cdef20b..0000000000 --- a/api/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/src/email-scan/loaders/load-dkim-by-key.js b/api/src/email-scan/loaders/load-dkim-by-key.js deleted file mode 100644 index 5db0b8b16f..0000000000 --- a/api/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/src/email-scan/loaders/load-dkim-connections-by-domain-id.js b/api/src/email-scan/loaders/load-dkim-connections-by-domain-id.js deleted file mode 100644 index 248c73f368..0000000000 --- a/api/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/src/email-scan/loaders/load-dkim-result-by-key.js b/api/src/email-scan/loaders/load-dkim-result-by-key.js deleted file mode 100644 index 064730e6ca..0000000000 --- a/api/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/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js b/api/src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js deleted file mode 100644 index 365feb51fa..0000000000 --- a/api/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/src/email-scan/loaders/load-dmarc-by-key.js b/api/src/email-scan/loaders/load-dmarc-by-key.js deleted file mode 100644 index bb8128b91d..0000000000 --- a/api/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/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js b/api/src/email-scan/loaders/load-dmarc-connections-by-domain-id.js deleted file mode 100644 index 0c31cc578c..0000000000 --- a/api/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/src/email-scan/loaders/load-spf-by-key.js b/api/src/email-scan/loaders/load-spf-by-key.js deleted file mode 100644 index 93e3ca4098..0000000000 --- a/api/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/src/email-scan/loaders/load-spf-connections-by-domain-id.js b/api/src/email-scan/loaders/load-spf-connections-by-domain-id.js deleted file mode 100644 index 8db41564d8..0000000000 --- a/api/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/src/email-scan/objects/__tests__/dkim-connection.test.js b/api/src/email-scan/objects/__tests__/dkim-connection.test.js deleted file mode 100644 index 61bc35b078..0000000000 --- a/api/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/src/email-scan/objects/__tests__/dkim-result-sub.test.js b/api/src/email-scan/objects/__tests__/dkim-result-sub.test.js deleted file mode 100644 index e0fb840f44..0000000000 --- a/api/src/email-scan/objects/__tests__/dkim-result-sub.test.js +++ /dev/null @@ -1,352 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -import { loadDkimGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { guidanceTagType } from '../../../guidance-tag/objects' -import { dkimResultSubType } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/objects/__tests__/dkim-results-connection.test.js b/api/src/email-scan/objects/__tests__/dkim-results-connection.test.js deleted file mode 100644 index 623744085d..0000000000 --- a/api/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/src/email-scan/objects/__tests__/dkim-results.test.js b/api/src/email-scan/objects/__tests__/dkim-results.test.js deleted file mode 100644 index 9befe68b4f..0000000000 --- a/api/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/src/email-scan/objects/__tests__/dkim-sub.test.js b/api/src/email-scan/objects/__tests__/dkim-sub.test.js deleted file mode 100644 index e46c9ac31d..0000000000 --- a/api/src/email-scan/objects/__tests__/dkim-sub.test.js +++ /dev/null @@ -1,194 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLID, GraphQLList } from 'graphql' - -import { dkimResultSubType, dkimSubType } from '../index' -import { domainType } from '../../../domain/objects' -import { StatusEnum } from '../../../enums' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - - 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/src/email-scan/objects/__tests__/dkim.test.js b/api/src/email-scan/objects/__tests__/dkim.test.js deleted file mode 100644 index 1b4b7e385c..0000000000 --- a/api/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/src/email-scan/objects/__tests__/dmarc-connection.test.js b/api/src/email-scan/objects/__tests__/dmarc-connection.test.js deleted file mode 100644 index 833ffaa7b1..0000000000 --- a/api/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/src/email-scan/objects/__tests__/dmarc-sub.test.js b/api/src/email-scan/objects/__tests__/dmarc-sub.test.js deleted file mode 100644 index 0bfe18aceb..0000000000 --- a/api/src/email-scan/objects/__tests__/dmarc-sub.test.js +++ /dev/null @@ -1,429 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList, GraphQLInt, GraphQLID } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -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' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/objects/__tests__/dmarc.test.js b/api/src/email-scan/objects/__tests__/dmarc.test.js deleted file mode 100644 index c70bd167ec..0000000000 --- a/api/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/src/email-scan/objects/__tests__/email-scan.test.js b/api/src/email-scan/objects/__tests__/email-scan.test.js deleted file mode 100644 index 210d5600d0..0000000000 --- a/api/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/src/email-scan/objects/__tests__/spf-connection.test.js b/api/src/email-scan/objects/__tests__/spf-connection.test.js deleted file mode 100644 index 1caf88f637..0000000000 --- a/api/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/src/email-scan/objects/__tests__/spf-sub.test.js b/api/src/email-scan/objects/__tests__/spf-sub.test.js deleted file mode 100644 index fad8526c5f..0000000000 --- a/api/src/email-scan/objects/__tests__/spf-sub.test.js +++ /dev/null @@ -1,415 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList, GraphQLInt, GraphQLID } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -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' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/email-scan/objects/__tests__/spf.test.js b/api/src/email-scan/objects/__tests__/spf.test.js deleted file mode 100644 index b430c70d93..0000000000 --- a/api/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/src/email-scan/objects/dkim-connection.js b/api/src/email-scan/objects/dkim-connection.js deleted file mode 100644 index dd2fb17e85..0000000000 --- a/api/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/src/email-scan/objects/dkim-result-connection.js b/api/src/email-scan/objects/dkim-result-connection.js deleted file mode 100644 index dd3d5ae442..0000000000 --- a/api/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/src/email-scan/objects/dkim-result-sub.js b/api/src/email-scan/objects/dkim-result-sub.js deleted file mode 100644 index 1732dcdd88..0000000000 --- a/api/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/src/email-scan/objects/dkim-result.js b/api/src/email-scan/objects/dkim-result.js deleted file mode 100644 index 6ffa55b070..0000000000 --- a/api/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/src/email-scan/objects/dkim-sub.js b/api/src/email-scan/objects/dkim-sub.js deleted file mode 100644 index b0d9b4783a..0000000000 --- a/api/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/src/email-scan/objects/dkim.js b/api/src/email-scan/objects/dkim.js deleted file mode 100644 index 4b2421e0d9..0000000000 --- a/api/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/src/email-scan/objects/dmarc-connection.js b/api/src/email-scan/objects/dmarc-connection.js deleted file mode 100644 index a76f3cb858..0000000000 --- a/api/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/src/email-scan/objects/dmarc-sub.js b/api/src/email-scan/objects/dmarc-sub.js deleted file mode 100644 index 4fd8e09427..0000000000 --- a/api/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/src/email-scan/objects/dmarc.js b/api/src/email-scan/objects/dmarc.js deleted file mode 100644 index 9703be47b9..0000000000 --- a/api/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/src/email-scan/objects/email-scan.js b/api/src/email-scan/objects/email-scan.js deleted file mode 100644 index 4111fa10a9..0000000000 --- a/api/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/src/email-scan/objects/index.js b/api/src/email-scan/objects/index.js deleted file mode 100644 index dc9d20afb9..0000000000 --- a/api/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/src/email-scan/objects/spf-connection.js b/api/src/email-scan/objects/spf-connection.js deleted file mode 100644 index 90e45a735b..0000000000 --- a/api/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/src/email-scan/objects/spf-sub.js b/api/src/email-scan/objects/spf-sub.js deleted file mode 100644 index 99773d43d9..0000000000 --- a/api/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/src/email-scan/objects/spf.js b/api/src/email-scan/objects/spf.js deleted file mode 100644 index b0b5e71276..0000000000 --- a/api/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/src/email-scan/subscriptions/__tests__/dkim-scan-data.test.js b/api/src/email-scan/subscriptions/__tests__/dkim-scan-data.test.js deleted file mode 100644 index 35a69b1d3a..0000000000 --- a/api/src/email-scan/subscriptions/__tests__/dkim-scan-data.test.js +++ /dev/null @@ -1,349 +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 { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadDkimGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - - 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/src/email-scan/subscriptions/__tests__/dmarc-scan-data.test.js b/api/src/email-scan/subscriptions/__tests__/dmarc-scan-data.test.js deleted file mode 100644 index 60c8ec77cd..0000000000 --- a/api/src/email-scan/subscriptions/__tests__/dmarc-scan-data.test.js +++ /dev/null @@ -1,346 +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 { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadDmarcGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - - 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/src/email-scan/subscriptions/__tests__/spf-scan-data.test.js b/api/src/email-scan/subscriptions/__tests__/spf-scan-data.test.js deleted file mode 100644 index 1038a7c9cb..0000000000 --- a/api/src/email-scan/subscriptions/__tests__/spf-scan-data.test.js +++ /dev/null @@ -1,340 +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 { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadSpfGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - - 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/src/email-scan/subscriptions/dkim-scan-data.js b/api/src/email-scan/subscriptions/dkim-scan-data.js deleted file mode 100644 index dd0856293a..0000000000 --- a/api/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/src/email-scan/subscriptions/dmarc-scan-data.js b/api/src/email-scan/subscriptions/dmarc-scan-data.js deleted file mode 100644 index f72fb0c729..0000000000 --- a/api/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/src/email-scan/subscriptions/index.js b/api/src/email-scan/subscriptions/index.js deleted file mode 100644 index 4e674b74c5..0000000000 --- a/api/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/src/email-scan/subscriptions/spf-scan-data.js b/api/src/email-scan/subscriptions/spf-scan-data.js deleted file mode 100644 index d35391f7df..0000000000 --- a/api/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/src/enums/affiliation-org-order-field.js b/api/src/enums/affiliation-org-order-field.js index b20c837099..8e362b59c3 100644 --- a/api/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 index 75d9967e87..c7cc3a41fe 100644 --- a/api/src/enums/affiliation-user-order-field.js +++ b/api/src/enums/affiliation-user-order-field.js @@ -4,9 +4,17 @@ 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.', + 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/src/enums/dkim-order-field.js b/api/src/enums/dkim-order-field.js index 7392508922..ed8565ec6e 100644 --- a/api/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/src/enums/dkim-result-order-field.js b/api/src/enums/dkim-result-order-field.js index b79cd5bf84..e24c2f4208 100644 --- a/api/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/src/enums/dmarc-order-field.js b/api/src/enums/dmarc-order-field.js index 0e5c4eb169..daf0a3688b 100644 --- a/api/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/src/enums/dmarc-summary-order-field.js b/api/src/enums/dmarc-summary-order-field.js index d122ac6cc0..6474dfda1b 100644 --- a/api/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 index 56a94941dd..ea8c60b807 100644 --- a/api/src/enums/domain-order-field.js +++ b/api/src/enums/domain-order-field.js @@ -4,6 +4,10 @@ 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.', 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/src/enums/guidance-tag-order-field.js b/api/src/enums/guidance-tag-order-field.js index 092398e983..8883a0f160 100644 --- a/api/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/src/enums/https-order-field.js b/api/src/enums/https-order-field.js index 119c5df118..586744370d 100644 --- a/api/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 index 5170a1df5b..3cf345b67e 100644 --- a/api/src/enums/index.js +++ b/api/src/enums/index.js @@ -1,21 +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/src/enums/languages.js b/api/src/enums/languages.js index 2dc1d76c26..ba41aa5c4c 100644 --- a/api/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/src/enums/order-direction.js b/api/src/enums/order-direction.js index b03e2cfbce..ebde2fc76b 100644 --- a/api/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/src/enums/organization-order-field.js b/api/src/enums/organization-order-field.js index b4fa30bf71..0176790b7e 100644 --- a/api/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/src/enums/period.js b/api/src/enums/period.js index 1ffe28f90b..b215298965 100644 --- a/api/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 index a5cb871876..a9878aaaa0 100644 --- a/api/src/enums/roles.js +++ b/api/src/enums/roles.js @@ -3,6 +3,10 @@ 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.', @@ -12,10 +16,18 @@ export const RoleEnums = new GraphQLEnumType({ 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: '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/src/enums/scan-types.js b/api/src/enums/scan-types.js index ce52c85d2c..fab976e1eb 100644 --- a/api/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/src/enums/spf-order-field.js b/api/src/enums/spf-order-field.js index 2a66069158..c02dcb34f3 100644 --- a/api/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/src/enums/ssl-order-field.js b/api/src/enums/ssl-order-field.js index 77d0d0c2a1..e83fc69cc9 100644 --- a/api/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/src/enums/status.js b/api/src/enums/status.js index fdc0672a1e..aa409d139f 100644 --- a/api/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/src/enums/tfa-send-method.js b/api/src/enums/tfa-send-method.js index a0e9179cca..27958d7ce0 100644 --- a/api/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/src/enums/verified-domain-order-field.js b/api/src/enums/verified-domain-order-field.js index 4d00139762..a0bcc20d2c 100644 --- a/api/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/src/enums/verified-organization-order-field.js b/api/src/enums/verified-organization-order-field.js index 0f1091df5d..d358e64834 100644 --- a/api/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 index 6d2a5b5879..52de3be3af 100644 --- a/api/src/guidance-tag/index.js +++ b/api/src/guidance-tag/index.js @@ -1,3 +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 index c67d01c637..a5d55dc818 100644 --- a/api/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js +++ b/api/src/guidance-tag/inputs/__tests__/guidance-tag-order.test.js @@ -9,17 +9,13 @@ describe('given the guidanceTagOrder input object', () => { const demoType = guidanceTagOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(GuidanceTagOrderField), - ) + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(GuidanceTagOrderField)) }) }) }) diff --git a/api/src/guidance-tag/inputs/guidance-tag-order.js b/api/src/guidance-tag/inputs/guidance-tag-order.js index d32253cdb0..1cdd0e443d 100644 --- a/api/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/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags-connections.test.js b/api/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags-connections.test.js deleted file mode 100644 index 0bd1cccbed..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags-connections.test.js +++ /dev/null @@ -1,2245 +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 { cleanseInput } from '../../../validators' -import { - loadAggregateGuidanceTagByTagId, - loadAggregateGuidanceTagConnectionsByTagId, -} from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - - 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/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags.test.js b/api/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags.test.js deleted file mode 100644 index 3a53380fcd..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-aggregate-guidance-tags.test.js +++ /dev/null @@ -1,407 +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 { loadAggregateGuidanceTagByTagId } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags-connections.test.js b/api/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags-connections.test.js deleted file mode 100644 index 1f6d4d04a2..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags-connections.test.js +++ /dev/null @@ -1,1945 +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 { cleanseInput } from '../../../validators' -import { - loadDkimGuidanceTagConnectionsByTagId, - loadDkimGuidanceTagByTagId, -} from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - 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/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags.test.js b/api/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags.test.js deleted file mode 100644 index feceb028fd..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-dkim-guidance-tags.test.js +++ /dev/null @@ -1,406 +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 { loadDkimGuidanceTagByTagId } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags-connections.test.js b/api/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags-connections.test.js deleted file mode 100644 index 1821d7d36f..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags-connections.test.js +++ /dev/null @@ -1,1972 +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 { cleanseInput } from '../../../validators' -import { - loadDmarcGuidanceTagConnectionsByTagId, - loadDmarcGuidanceTagByTagId, -} from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - - 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/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags.test.js b/api/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags.test.js deleted file mode 100644 index ce8bd831a6..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-dmarc-guidance-tags.test.js +++ /dev/null @@ -1,406 +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 { loadDmarcGuidanceTagByTagId } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/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/__tests__/load-https-guidance-tags-connections.test.js b/api/src/guidance-tag/loaders/__tests__/load-https-guidance-tags-connections.test.js deleted file mode 100644 index f843b46b03..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-https-guidance-tags-connections.test.js +++ /dev/null @@ -1,1970 +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 { cleanseInput } from '../../../validators' -import { - loadHttpsGuidanceTagConnectionsByTagId, - loadHttpsGuidanceTagByTagId, -} from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - 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/src/guidance-tag/loaders/__tests__/load-https-guidance-tags.test.js b/api/src/guidance-tag/loaders/__tests__/load-https-guidance-tags.test.js deleted file mode 100644 index f92488dbf4..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-https-guidance-tags.test.js +++ /dev/null @@ -1,406 +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 { loadHttpsGuidanceTagByTagId } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags-connections.test.js b/api/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags-connections.test.js deleted file mode 100644 index ee983b3e48..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags-connections.test.js +++ /dev/null @@ -1,1956 +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 { cleanseInput } from '../../../validators' -import { - loadSpfGuidanceTagConnectionsByTagId, - loadSpfGuidanceTagByTagId, -} from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - - 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/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags.test.js b/api/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags.test.js deleted file mode 100644 index 18b386a498..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-spf-guidance-tags.test.js +++ /dev/null @@ -1,406 +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 { loadSpfGuidanceTagByTagId } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags-connections.test.js b/api/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags-connections.test.js deleted file mode 100644 index cb3b744657..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags-connections.test.js +++ /dev/null @@ -1,1956 +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 { cleanseInput } from '../../../validators' -import { - loadSslGuidanceTagConnectionsByTagId, - loadSslGuidanceTagByTagId, -} from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - - 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/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags.test.js b/api/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags.test.js deleted file mode 100644 index ca0782ef96..0000000000 --- a/api/src/guidance-tag/loaders/__tests__/load-ssl-guidance-tags.test.js +++ /dev/null @@ -1,406 +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 { loadSslGuidanceTagByTagId } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/guidance-tag/loaders/index.js b/api/src/guidance-tag/loaders/index.js index ea70fd106b..88a9f34847 100644 --- a/api/src/guidance-tag/loaders/index.js +++ b/api/src/guidance-tag/loaders/index.js @@ -10,3 +10,5 @@ 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 index 2015f70f4d..f06f540a22 100644 --- a/api/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js +++ b/api/src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js @@ -1,205 +1,205 @@ -import { t } from '@lingui/macro' -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' +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`>` - } + ({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})` + 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` - } + 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` + 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`<` - } + 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})` + 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` - } + 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` + 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' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAggregateGuidanceTagConnectionsByTagId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAggregateGuidanceTagConnectionsByTagId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAggregateGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, ), ) - } 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 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}\`.`), + ) } - } 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])` + 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`<` - } + 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` - } + 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` + 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` + 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 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 sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let aggregateGuidanceTagInfoCursor - try { - aggregateGuidanceTagInfoCursor = await query` + let aggregateGuidanceTagInfoCursor + try { + aggregateGuidanceTagInfoCursor = await query` WITH aggregateGuidanceTags ${afterVar} ${beforeVar} - + LET retrievedAggregateGuidanceTags = ( FOR tag IN aggregateGuidanceTags FILTER tag._key IN ${aggregateGuidanceTags} @@ -236,7 +236,7 @@ export const loadAggregateGuidanceTagConnectionsByTagId = 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}), @@ -246,56 +246,56 @@ export const loadAggregateGuidanceTagConnectionsByTagId = "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.`), - ) - } + } 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.`), - ) - } + 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, + })) - if (aggregateGuidanceTagInfo.aggregateGuidanceTags.length === 0) { return { - edges: [], - totalCount: 0, + edges, + totalCount: aggregateGuidanceTagInfo.totalCount, pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', + hasNextPage: aggregateGuidanceTagInfo.hasNextPage, + hasPreviousPage: aggregateGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId( + 'guidanceTag', + aggregateGuidanceTagInfo.startKey, + ), + endCursor: toGlobalId('guidanceTag', aggregateGuidanceTagInfo.endKey), }, } } - - 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/src/guidance-tag/loaders/load-aggregate-guidance-tags.js b/api/src/guidance-tag/loaders/load-aggregate-guidance-tags.js index 90cefe4cae..7fdeb1139e 100644 --- a/api/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 index 08ff18f97c..7bcb9a30a9 100644 --- a/api/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js +++ b/api/src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js @@ -1,211 +1,211 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' +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`>` + ({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 { - afterTemplateDirection = aql`<` - } + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } - afterVar = aql`LET afterVar = DOCUMENT(dkimGuidanceTags, ${afterId})` + 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` - } + 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` + 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`<` + 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 { - beforeTemplateDirection = aql`>` - } + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } - beforeVar = aql`LET beforeVar = DOCUMENT(dkimGuidanceTags, ${beforeId})` + 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` - } + 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` + 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' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDkimGuidanceTagConnectionsByTagId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDkimGuidanceTagConnectionsByTagId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDkimGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, ), ) - } 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 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 { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + 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 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` - } + 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` + 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` + 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 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 sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let dkimGuidanceTagInfoCursor - try { - dkimGuidanceTagInfoCursor = await query` + let dkimGuidanceTagInfoCursor + try { + dkimGuidanceTagInfoCursor = await query` WITH dkimGuidanceTags ${afterVar} ${beforeVar} - + LET retrievedDkimGuidanceTags = ( FOR tag IN dkimGuidanceTags FILTER tag._key IN ${dkimGuidanceTags} @@ -242,7 +242,7 @@ export const loadDkimGuidanceTagConnectionsByTagId = 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}), @@ -252,53 +252,53 @@ export const loadDkimGuidanceTagConnectionsByTagId = "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.`), - ) - } + } 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.`), - ) - } + 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, + })) - if (dkimGuidanceTagInfo.dkimGuidanceTags.length === 0) { return { - edges: [], - totalCount: 0, + edges, + totalCount: dkimGuidanceTagInfo.totalCount, pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', + hasNextPage: dkimGuidanceTagInfo.hasNextPage, + hasPreviousPage: dkimGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', dkimGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', dkimGuidanceTagInfo.endKey), }, } } - - 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/src/guidance-tag/loaders/load-dkim-guidance-tags.js b/api/src/guidance-tag/loaders/load-dkim-guidance-tags.js index 48866f9712..c791181c78 100644 --- a/api/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 index 27bf131ea5..7e81019e41 100644 --- a/api/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js +++ b/api/src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js @@ -1,211 +1,211 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' +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`>` + ({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 { - afterTemplateDirection = aql`<` - } + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } - afterVar = aql`LET afterVar = DOCUMENT(dmarcGuidanceTags, ${afterId})` + 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` - } + 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` + 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`<` + 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 { - beforeTemplateDirection = aql`>` - } + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } - beforeVar = aql`LET beforeVar = DOCUMENT(dmarcGuidanceTags, ${beforeId})` + 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` - } + 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` + 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' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcGuidanceTagConnectionsByTagId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcGuidanceTagConnectionsByTagId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, ), ) - } 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 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 { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + 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 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` - } + 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` + 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` + 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 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 sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let dmarcGuidanceTagInfoCursor - try { - dmarcGuidanceTagInfoCursor = await query` + let dmarcGuidanceTagInfoCursor + try { + dmarcGuidanceTagInfoCursor = await query` WITH dmarcGuidanceTags ${afterVar} ${beforeVar} - + LET retrievedDmarcGuidanceTags = ( FOR tag IN dmarcGuidanceTags FILTER tag._key IN ${dmarcGuidanceTags} @@ -242,7 +242,7 @@ export const loadDmarcGuidanceTagConnectionsByTagId = 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}), @@ -252,53 +252,53 @@ export const loadDmarcGuidanceTagConnectionsByTagId = "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.`), - ) - } + } 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.`), - ) - } + 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, + })) - if (dmarcGuidanceTagInfo.dmarcGuidanceTags.length === 0) { return { - edges: [], - totalCount: 0, + edges, + totalCount: dmarcGuidanceTagInfo.totalCount, pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', + hasNextPage: dmarcGuidanceTagInfo.hasNextPage, + hasPreviousPage: dmarcGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', dmarcGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', dmarcGuidanceTagInfo.endKey), }, } } - - 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/src/guidance-tag/loaders/load-dmarc-guidance-tags.js b/api/src/guidance-tag/loaders/load-dmarc-guidance-tags.js index 343f27099f..383539e261 100644 --- a/api/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 index 09b9c1a1af..8cd3f5530e 100644 --- a/api/src/guidance-tag/loaders/load-https-guidance-tags-connections.js +++ b/api/src/guidance-tag/loaders/load-https-guidance-tags-connections.js @@ -1,211 +1,211 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' +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`>` + ({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 { - afterTemplateDirection = aql`<` - } + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } - afterVar = aql`LET afterVar = DOCUMENT(httpsGuidanceTags, ${afterId})` + 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` - } + 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` + 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`<` + 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 { - beforeTemplateDirection = aql`>` - } + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } - beforeVar = aql`LET beforeVar = DOCUMENT(httpsGuidanceTags, ${beforeId})` + 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` - } + 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` + 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' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadHttpsGuidanceTagConnectionsByTagId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadHttpsGuidanceTagConnectionsByTagId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadHttpsGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, ), ) - } 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 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 { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + 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 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` - } + 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` + 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` + 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 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 sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let httpsGuidanceTagInfoCursor - try { - httpsGuidanceTagInfoCursor = await query` + let httpsGuidanceTagInfoCursor + try { + httpsGuidanceTagInfoCursor = await query` WITH httpsGuidanceTags ${afterVar} ${beforeVar} - + LET retrievedHttpsGuidanceTags = ( FOR tag IN httpsGuidanceTags FILTER tag._key IN ${httpsGuidanceTags} @@ -242,7 +242,7 @@ export const loadHttpsGuidanceTagConnectionsByTagId = 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}), @@ -252,53 +252,53 @@ export const loadHttpsGuidanceTagConnectionsByTagId = "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.`), - ) - } + } 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.`), - ) - } + 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, + })) - if (httpsGuidanceTagInfo.httpsGuidanceTags.length === 0) { return { - edges: [], - totalCount: 0, + edges, + totalCount: httpsGuidanceTagInfo.totalCount, pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', + hasNextPage: httpsGuidanceTagInfo.hasNextPage, + hasPreviousPage: httpsGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', httpsGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', httpsGuidanceTagInfo.endKey), }, } } - - 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/src/guidance-tag/loaders/load-https-guidance-tags.js b/api/src/guidance-tag/loaders/load-https-guidance-tags.js index 40221395f1..a66d0e03ff 100644 --- a/api/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 index d131630ce7..20e50ebc49 100644 --- a/api/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js +++ b/api/src/guidance-tag/loaders/load-spf-guidance-tags-connections.js @@ -1,211 +1,211 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' +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`>` + ({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 { - afterTemplateDirection = aql`<` - } + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } - afterVar = aql`LET afterVar = DOCUMENT(spfGuidanceTags, ${afterId})` + 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` - } + 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` + 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`<` + 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 { - beforeTemplateDirection = aql`>` - } + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } - beforeVar = aql`LET beforeVar = DOCUMENT(spfGuidanceTags, ${beforeId})` + 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` - } + 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` + 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' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSpfGuidanceTagConnectionsByTagId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSpfGuidanceTagConnectionsByTagId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadSpfGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, ), ) - } 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 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 { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + 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 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` - } + 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` + 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` + 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 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 sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let spfGuidanceTagInfoCursor - try { - spfGuidanceTagInfoCursor = await query` + let spfGuidanceTagInfoCursor + try { + spfGuidanceTagInfoCursor = await query` WITH spfGuidanceTags ${afterVar} ${beforeVar} - + LET retrievedSpfGuidanceTags = ( FOR tag IN spfGuidanceTags FILTER tag._key IN ${spfGuidanceTags} @@ -242,7 +242,7 @@ export const loadSpfGuidanceTagConnectionsByTagId = 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}), @@ -252,53 +252,53 @@ export const loadSpfGuidanceTagConnectionsByTagId = "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.`), - ) - } + } 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.`), - ) - } + 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, + })) - if (spfGuidanceTagInfo.spfGuidanceTags.length === 0) { return { - edges: [], - totalCount: 0, + edges, + totalCount: spfGuidanceTagInfo.totalCount, pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', + hasNextPage: spfGuidanceTagInfo.hasNextPage, + hasPreviousPage: spfGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', spfGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', spfGuidanceTagInfo.endKey), }, } } - - 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/src/guidance-tag/loaders/load-spf-guidance-tags.js b/api/src/guidance-tag/loaders/load-spf-guidance-tags.js index 484766c33c..1694328715 100644 --- a/api/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 index c9af366d3a..cb68602cdb 100644 --- a/api/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js +++ b/api/src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js @@ -1,206 +1,206 @@ -import { aql } from 'arangojs' -import { fromGlobalId, toGlobalId } from 'graphql-relay' -import { t } from '@lingui/macro' +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`>` + ({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 { - afterTemplateDirection = aql`<` - } + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } - afterVar = aql`LET afterVar = DOCUMENT(sslGuidanceTags, ${afterId})` + 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` - } + 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` + 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`<` + 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 { - beforeTemplateDirection = aql`>` - } + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } - beforeVar = aql`LET beforeVar = DOCUMENT(sslGuidanceTags, ${beforeId})` + 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` - } + 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` + 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' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadSslGuidanceTagConnectionsByTagId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`GuidanceTag\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`GuidanceTag\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadSslGuidanceTagConnectionsByTagId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadSslGuidanceTagConnectionsByTagId.`, ) throw new Error( i18n._( - t`Requesting \`${amount}\` records on the \`GuidanceTag\` connection exceeds the \`${argSet}\` limit of 100 records.`, + t`Passing both \`first\` and \`last\` to paginate the \`GuidanceTag\` connection is not supported.`, ), ) - } 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 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 { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + 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 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` - } + 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` + 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` + 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 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 sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let sslGuidanceTagInfoCursor - try { - sslGuidanceTagInfoCursor = await query` + let sslGuidanceTagInfoCursor + try { + sslGuidanceTagInfoCursor = await query` WITH sslGuidanceTags ${afterVar} @@ -242,7 +242,7 @@ export const loadSslGuidanceTagConnectionsByTagId = 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}), @@ -252,53 +252,53 @@ export const loadSslGuidanceTagConnectionsByTagId = "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.`), - ) - } + } 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.`), - ) - } + 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, + })) - if (sslGuidanceTagInfo.sslGuidanceTags.length === 0) { return { - edges: [], - totalCount: 0, + edges, + totalCount: sslGuidanceTagInfo.totalCount, pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', + hasNextPage: sslGuidanceTagInfo.hasNextPage, + hasPreviousPage: sslGuidanceTagInfo.hasPreviousPage, + startCursor: toGlobalId('guidanceTag', sslGuidanceTagInfo.startKey), + endCursor: toGlobalId('guidanceTag', sslGuidanceTagInfo.endKey), }, } } - - 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/src/guidance-tag/loaders/load-ssl-guidance-tags.js b/api/src/guidance-tag/loaders/load-ssl-guidance-tags.js index 7605cad603..1092e31f54 100644 --- a/api/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/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js b/api/src/guidance-tag/objects/__tests__/guidance-tag-connection.test.js index fe49ebac9c..28992c90bb 100644 --- a/api/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/src/guidance-tag/objects/__tests__/guidance-tag.test.js b/api/src/guidance-tag/objects/__tests__/guidance-tag.test.js index 0b5ee05714..3745705410 100644 --- a/api/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/src/guidance-tag/objects/__tests__/ref-links.test.js b/api/src/guidance-tag/objects/__tests__/ref-links.test.js index a46acb95f8..1de67ad91f 100644 --- a/api/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 index 1bc28e89f1..a6b8c50e2c 100644 --- a/api/src/guidance-tag/objects/guidance-tag-connection.js +++ b/api/src/guidance-tag/objects/guidance-tag-connection.js @@ -1,12 +1,14 @@ -import { GraphQLInt } from 'graphql' -import { connectionDefinitions } from 'graphql-relay' - +import { GraphQLInt, GraphQLObjectType, GraphQLList } from 'graphql' import { guidanceTagType } from './guidance-tag' -export const guidanceTagConnection = connectionDefinitions({ - name: 'GuidanceTag', - nodeType: guidanceTagType, - connectionFields: () => ({ +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.', diff --git a/api/src/guidance-tag/objects/guidance-tag.js b/api/src/guidance-tag/objects/guidance-tag.js index c18635b29d..687c5073da 100644 --- a/api/src/guidance-tag/objects/guidance-tag.js +++ b/api/src/guidance-tag/objects/guidance-tag.js @@ -1,4 +1,4 @@ -import { GraphQLList, GraphQLObjectType, GraphQLString } from 'graphql' +import { GraphQLList, GraphQLObjectType, GraphQLString, GraphQLInt } from 'graphql' import { globalIdField } from 'graphql-relay' import { refLinksType } from './ref-links' @@ -6,8 +6,7 @@ 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', + description: 'Details for a given guidance tag based on https://github.com/canada-ca/tracker/wiki/Guidance-Tags', fields: () => ({ id: globalIdField('guidanceTag'), tagId: { @@ -22,20 +21,24 @@ export const guidanceTagType = new GraphQLObjectType({ }, guidance: { type: GraphQLString, - description: - 'Guidance for changes to record, or to maintain current stance.', + description: 'Guidance for changes to record, or to maintain current stance.', resolve: ({ guidance }) => guidance, }, refLinks: { - type: GraphQLList(refLinksType), + type: new GraphQLList(refLinksType), description: 'Links to implementation guidance for a given tag.', resolve: ({ refLinksGuide }) => refLinksGuide, }, refLinksTech: { - type: GraphQLList(refLinksType), + 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/src/guidance-tag/objects/ref-links.js b/api/src/guidance-tag/objects/ref-links.js index 73bfe6faad..e2c4644657 100644 --- a/api/src/guidance-tag/objects/ref-links.js +++ b/api/src/guidance-tag/objects/ref-links.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const refLinksType = new GraphQLObjectType({ name: 'RefLinks', @@ -8,12 +8,12 @@ export const refLinksType = new GraphQLObjectType({ description: { type: GraphQLString, description: 'Title of the guidance link.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, refLink: { type: GraphQLString, description: 'URL for the guidance documentation.', - resolve: ({ ref_link: refLink }) => refLink, + resolve: ({ref_link: refLink}) => refLink, }, }), }) diff --git a/api/src/initialize-loaders.js b/api/src/initialize-loaders.js index 51fb9915b4..1ab26ba52e 100644 --- a/api/src/initialize-loaders.js +++ b/api/src/initialize-loaders.js @@ -13,50 +13,17 @@ import { loadSpfFailureConnectionsBySumId, loadStartDateFromPeriod, loadDmarcYearlySumEdge, + loadAllVerifiedRuaDomains, } from './dmarc-summaries/loaders' import { loadDomainByKey, loadDomainByDomain, loadDomainConnectionsByOrgId, loadDomainConnectionsByUserId, + loadDkimSelectorsByDomainId, } 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 { loadOrgByKey, loadOrganizationNamesById } from './organization/loaders' +import { loadMyTrackerByUserId, loadUserByUserName, loadUserByKey, loadUserConnectionsByUserId } from './user/loaders' import { loadVerifiedDomainsById, loadVerifiedDomainByKey, @@ -69,33 +36,22 @@ import { loadVerifiedOrgConnectionsByDomainId, loadVerifiedOrgConnections, } from './verified-organizations/loaders' -import { loadChartSummaryByKey } from './summaries/loaders' +import { loadTagByTagId, loadTagsByOrg } from './tags' -export function initializeLoaders({ - query, - userKey, - i18n, - language, - cleanseInput, - loginRequiredBool, - moment, -}) { +export function initializeLoaders({ query, userKey, i18n, language, cleanseInput, loginRequiredBool, moment }) { return { - loadChartSummaryByKey: loadChartSummaryByKey({ query, userKey, i18n }), - loadAggregateGuidanceTagByTagId: loadAggregateGuidanceTagByTagId({ + loadTagByTagId: loadTagByTagId({ + query, + userKey, + i18n, + language, + }), + loadTagsByOrg: loadTagsByOrg({ query, userKey, i18n, language, }), - loadAggregateGuidanceTagConnectionsByTagId: - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey, - i18n, - cleanseInput, - language, - }), loadDkimFailConnectionsBySumId: loadDkimFailConnectionsBySumId({ query, userKey, @@ -120,12 +76,11 @@ export function initializeLoaders({ i18n, }), }), - loadDmarcSummaryEdgeByDomainIdAndPeriod: - loadDmarcSummaryEdgeByDomainIdAndPeriod({ - query, - userKey, - i18n, - }), + loadDmarcSummaryEdgeByDomainIdAndPeriod: loadDmarcSummaryEdgeByDomainIdAndPeriod({ + query, + userKey, + i18n, + }), loadDmarcSummaryByKey: loadDmarcSummaryByKey({ query, userKey, i18n }), loadFullPassConnectionsBySumId: loadFullPassConnectionsBySumId({ query, @@ -150,6 +105,7 @@ export function initializeLoaders({ loadDomainConnectionsByOrgId: loadDomainConnectionsByOrgId({ query, userKey, + language, cleanseInput, i18n, auth: { loginRequiredBool }, @@ -161,143 +117,28 @@ export function initializeLoaders({ i18n, auth: { loginRequiredBool }, }), - 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, - }), - loadDkimGuidanceTagConnectionsByTagId: - loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language, - }), - loadDmarcGuidanceTagByTagId: loadDmarcGuidanceTagByTagId({ - query, - userKey, - i18n, - language, - }), - loadDmarcGuidanceTagConnectionsByTagId: - loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language, - }), - loadHttpsGuidanceTagByTagId: loadHttpsGuidanceTagByTagId({ - query, - userKey, - i18n, - language, - }), - loadHttpsGuidanceTagConnectionsByTagId: - loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language, - }), - loadSpfGuidanceTagByTagId: loadSpfGuidanceTagByTagId({ - query, - userKey, - i18n, - language, - }), - loadSpfGuidanceTagConnectionsByTagId: loadSpfGuidanceTagConnectionsByTagId({ + loadDkimSelectorsByDomainId: loadDkimSelectorsByDomainId({ query, userKey, cleanseInput, i18n, - language, - }), - loadSslGuidanceTagByTagId: loadSslGuidanceTagByTagId({ - query, - userKey, - i18n, - language, - }), - loadSslGuidanceTagConnectionsByTagId: loadSslGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language, - }), - loadOrgByKey: loadOrgByKey({ - query, - language, - userKey, - i18n, - }), - loadOrgBySlug: loadOrgBySlug({ - query, - language, - userKey, - i18n, + auth: { loginRequiredBool }, }), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ + loadOrgByKey: loadOrgByKey({ query, language, userKey, i18n }), + loadOrganizationNamesById: loadOrganizationNamesById({ query, userKey, i18n }), + loadMyTrackerByUserId: loadMyTrackerByUserId({ query, language, userKey, - cleanseInput, i18n, - auth: { loginRequiredBool }, }), - loadOrgConnectionsByUserId: loadOrgConnectionsByUserId({ + loadUserByUserName: loadUserByUserName({ query, userKey, i18n }), + loadUserConnectionsByUserId: loadUserConnectionsByUserId({ query, userKey, cleanseInput, - language, i18n, - auth: { loginRequiredBool }, }), - loadUserByUserName: loadUserByUserName({ query, userKey, i18n }), loadUserByKey: loadUserByKey({ query, userKey, i18n }), loadAffiliationByKey: loadAffiliationByKey({ query, userKey, i18n }), loadAffiliationConnectionsByUserId: loadAffiliationConnectionsByUserId({ @@ -347,5 +188,10 @@ export function initializeLoaders({ cleanseInput, i18n, }), + loadAllVerifiedRuaDomains: loadAllVerifiedRuaDomains({ + query, + userKey, + i18n, + }), } } diff --git a/api/src/locale/en/messages.js b/api/src/locale/en/messages.js index d202d4503e..1000ee7acc 100644 --- a/api/src/locale/en/messages.js +++ b/api/src/locale/en/messages.js @@ -1 +1 @@ -/*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 phase summary. Please try again.":"Unable to load DMARC phase summary. 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 HTTPS summary. Please try again.":"Unable to load HTTPS summary. 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."]}}; \ No newline at end of file +/*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 index 2be68a6217..a2b0b826bc 100644 --- a/api/src/locale/en/messages.po +++ b/api/src/locale/en/messages.po @@ -11,114 +11,289 @@ msgstr "" "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 +#: 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/organization/objects/organization.js:142 +#: 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/user/mutations/sign-up.js:116 +#: 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/user/mutations/send-password-reset.js:62 +#: 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: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/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:175 +#: 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:66 +#: 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:70 -#: src/user/mutations/sign-in.js:294 +#: 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/verify-jwt.js:14 +#: src/auth/utils/verify-jwt.js:15 msgid "Invalid token, please sign in." msgstr "Invalid token, please sign in." -#: src/user/mutations/reset-password.js:108 +#: 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:49 +#: 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:33 +#: 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:31 +#: 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:82 +#: 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:188 +#: 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:138 +#: 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/check-domain-ownership.js:33 -#: src/auth/check-domain-ownership.js:45 -#: src/auth/check-domain-ownership.js:67 -#: src/auth/check-domain-ownership.js:78 +#: 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:80 +#: 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-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." +#~ 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:198 +#: 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:160 -#: src/domain/loaders/load-domain-connections-by-user-id.js:160 +#: 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." @@ -136,26 +311,34 @@ msgid "Passing both `first` and `last` to paginate the `GuidanceTag` connection 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." +#~ 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:192 -#: src/organization/loaders/load-organization-connections-by-user-id.js:191 +#: 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/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." +#~ 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." @@ -166,124 +349,222 @@ msgstr "Passing both `first` and `last` to paginate the `VerifiedDomain` connect 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 +#: 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:168 +#: src/user/mutations/reset-password.js:140 msgid "Password was successfully reset." msgstr "Password was successfully reset." -#: src/user/mutations/update-user-password.js:134 +#: src/user/mutations/update-user-password.js:109 msgid "Password was successfully updated." msgstr "Password was successfully updated." -#: src/user/mutations/sign-up.js:102 +#: src/user/mutations/sign-up.js:99 msgid "Passwords do not match." msgstr "Passwords do not match." -#: src/organization/queries/find-organization-by-slug.js:61 -#: src/organization/queries/find-organization-by-slug.js:74 +#: 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:108 +#: 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:95 +#: 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-domain.js:110 +#: 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/organization/mutations/remove-organization.js:71 +#: 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:219 +#: 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/organization/mutations/update-organization.js:156 +#: 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/affiliation/mutations/update-user-role.js:191 -#: src/affiliation/mutations/update-user-role.js:214 -#: src/affiliation/mutations/update-user-role.js:232 -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/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/invite-user-to-org.js:113 +#: 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:113 +#: 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:102 +#: src/domain/mutations/create-domain.js:135 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:57 +#: 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-scan.js:70 +#: 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:113 +#: src/domain/mutations/update-domain.js:162 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:97 +#: 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/create-domain.js:157 +msgid "Permission Denied: Please contact super admin for help with creating domain." +msgstr "Permission Denied: Please contact super admin for help with creating domain." + +#: 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:85 +#: 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/organization/mutations/verify-organization.js:69 +#: 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/domain/mutations/update-domain.js:210 +msgid "Permission Denied: Please contact super admin for help with updating domain." +msgstr "Permission Denied: Please contact super admin for help with updating 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/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 +#: 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:54 +#: 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/user/mutations/remove-phone-number.js:81 +#: 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:131 +#: 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/user/mutations/update-user-profile.js:193 +#: 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/affiliation/loaders/load-affiliation-connections-by-org-id.js:103 +#: 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." @@ -296,12 +577,12 @@ msgstr "Requesting `{amount}` records on the `DkimFailureTable` connection excee 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:221 +#: 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:183 -#: src/domain/loaders/load-domain-connections-by-user-id.js:183 +#: 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." @@ -318,8 +599,12 @@ msgstr "Requesting `{amount}` records on the `FullPassTable` connection exceeds 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:215 -#: src/organization/loaders/load-organization-connections-by-user-id.js:214 +#: 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." @@ -327,6 +612,10 @@ msgstr "Requesting `{amount}` records on the `Organization` connection exceeds t 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." @@ -337,63 +626,84 @@ msgstr "Requesting `{amount}` records on the `VerifiedDomain` connection exceeds 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." +#. 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/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/domain/mutations/add-organizations-domains.js:351 +#~ msgid "Successfully added {domainCount} domains to {0}." +#~ msgstr "Successfully added {domainCount} domains to {0}." -#: 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." +#. 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:508 +#: 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:131 +#: src/domain/mutations/request-scan.js:175 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/domain/mutations/request-discovery.js:134 +msgid "Successfully dispatched subdomain discovery scan." +msgstr "Successfully dispatched subdomain discovery scan." -#: src/affiliation/mutations/invite-user-to-org.js:202 +#: 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." -#: src/affiliation/mutations/leave-organization.js:444 +#. placeholder {0}: org.slug +#: src/affiliation/mutations/leave-organization.js:86 msgid "Successfully left organization: {0}" msgstr "Successfully left organization: {0}" -#: src/domain/mutations/remove-domain.js:403 +#. 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}." -#: src/organization/mutations/remove-organization.js:471 +#. 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:205 +#: src/affiliation/mutations/remove-user-from-org.js:201 msgid "Successfully removed user from organization." msgstr "Successfully removed user from organization." -#: src/affiliation/mutations/invite-user-to-org.js:144 +#: 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." @@ -401,115 +711,187 @@ msgstr "Successfully sent invitation to service, and organization email." msgid "Successfully signed out." msgstr "Successfully signed out." -#: src/affiliation/mutations/transfer-org-ownership.js:224 +#. 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}" -#: src/organization/mutations/verify-organization.js:157 +#. 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:118 +#: 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/user/mutations/authenticate.js:62 +#: 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:84 +#: 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/verify-phone-number.js:63 +#: 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:50 +#: 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: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 +#: 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/user/mutations/authenticate.js:77 -#: src/user/mutations/authenticate.js:123 -#: src/user/mutations/authenticate.js:132 +#: 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/check-permission.js:30 -#: src/auth/check-permission.js:58 -#: src/auth/check-super-admin.js:20 -#: src/auth/check-super-admin.js:30 +#: 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:67 +#: 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: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 +#: 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/domain/mutations/create-domain.js:82 +#: 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:121 msgid "Unable to create domain in unknown organization." msgstr "Unable to create domain in unknown organization." -#: src/domain/mutations/create-domain.js:159 +#: src/domain/mutations/create-domain.js:216 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:138 -#: src/domain/mutations/create-domain.js:148 -#: src/domain/mutations/create-domain.js:197 -#: src/domain/mutations/create-domain.js:207 -#: src/domain/mutations/create-domain.js:225 -#: src/domain/mutations/create-domain.js:255 -#: src/domain/mutations/create-domain.js:273 -#: src/domain/mutations/create-domain.js:283 +#: src/domain/mutations/create-domain.js:198 +#: src/domain/mutations/create-domain.js:206 +#: src/domain/mutations/create-domain.js:238 +#: src/domain/mutations/create-domain.js:247 +#: src/domain/mutations/create-domain.js:267 +#: 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 +#: 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." +#~ 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 @@ -523,13 +905,13 @@ 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." +#~ 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." +#~ 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 @@ -538,44 +920,54 @@ 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." +#~ 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 +#: 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." +#~ 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 +#: 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." +#~ 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 +#: 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." +#~ 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:46 +#: src/domain/queries/find-domain-by-domain.js:41 msgid "Unable to find the requested domain." msgstr "Unable to find the requested domain." @@ -591,28 +983,73 @@ msgstr "Unable to find user affiliation(s). Please try again." 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:94 +#: 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:177 -#: src/affiliation/mutations/invite-user-to-org.js:192 +#: 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:80 +#: 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:51 +#: 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 @@ -626,13 +1063,17 @@ 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." +#~ 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." +#~ 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 @@ -645,17 +1086,17 @@ msgstr "Unable to load DMARC failure data. Please try again." 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:14 -msgid "Unable to load DMARC phase summary. Please try again." -msgstr "Unable to load DMARC phase summary. 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." +#~ 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:477 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:489 +#: 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 @@ -663,6 +1104,45 @@ msgstr "Unable to load DMARC scan(s). Please try again." 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." @@ -671,12 +1151,66 @@ 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." +#~ 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." +#~ 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 @@ -691,8 +1225,12 @@ 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." +#~ 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 @@ -701,66 +1239,36 @@ 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:391 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:401 -#: src/domain/loaders/load-domain-connections-by-user-id.js:413 -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:533 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:545 -#: src/organization/loaders/load-organization-connections-by-user-id.js:520 -#: src/organization/loaders/load-organization-connections-by-user-id.js:532 -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." +#~ 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." @@ -782,110 +1290,183 @@ msgstr "Unable to load verified domain(s). Please try again." 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." +#~ 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-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:403 +#: 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/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 +#: 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:123 +#: 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:80 +#: 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:126 -#: src/domain/mutations/remove-domain.js:140 -#: src/domain/mutations/remove-domain.js:179 -#: src/domain/mutations/remove-domain.js:198 +#: 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:360 -#: src/domain/mutations/remove-domain.js:383 -#: src/domain/mutations/remove-domain.js:394 +#: src/domain/mutations/remove-domain.js:345 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 +#: 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:63 -#: src/user/mutations/remove-phone-number.js:74 +#: 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:64 +#: 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:56 +#: 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:90 +#: 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:76 -msgid "Unable to remove user from organization." -msgstr "Unable to remove 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:110 -#: src/affiliation/mutations/remove-user-from-org.js:137 -#: src/affiliation/mutations/remove-user-from-org.js:181 -#: src/affiliation/mutations/remove-user-from-org.js:194 +#: 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:62 +#: 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:57 +#: 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/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 +#: 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:161 -#: src/domain/objects/domain.js:207 +#: 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}" @@ -902,179 +1483,255 @@ msgstr "Unable to send authentication email. Please try again." 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:31 -#: src/notify/notify-send-org-invite-email.js:26 +#: 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-password-reset-email.js:30 +#: 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." +#~ 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:30 +#: 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:108 -#: src/user/mutations/set-phone-number.js:117 +#: 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: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:275 -#: src/user/mutations/sign-in.js:284 +#: 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:204 -#: src/user/mutations/sign-up.js:218 +#: 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:172 -#: src/user/mutations/sign-up.js:182 -#: src/user/mutations/sign-up.js:240 -#: src/user/mutations/sign-up.js:250 +#: 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/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 +#: 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: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: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:112 +#: 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:65 +#: 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:144 +#: 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:92 -#: src/user/mutations/verify-phone-number.js:103 +#: 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/update-domain.js:94 +#: 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:284 +msgid "Unable to update domain edge. Please try again." +msgstr "Unable to update domain edge. Please try again." + +#: src/domain/mutations/update-domain.js:148 msgid "Unable to update domain in an unknown org." msgstr "Unable to update domain in an unknown org." -#: src/domain/mutations/update-domain.js:142 +#: src/domain/mutations/update-domain.js:189 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:131 -#: src/domain/mutations/update-domain.js:178 -#: src/domain/mutations/update-domain.js:188 +#: src/domain/mutations/update-domain.js:179 +#: src/domain/mutations/update-domain.js:241 +#: src/domain/mutations/update-domain.js:295 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 +#: 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:62 +#: 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:76 +#: 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:90 +#: 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:119 -#: src/user/mutations/update-user-password.js:128 +#: 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:167 -#: src/user/mutations/update-user-profile.js:176 +#: 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:98 +#: 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:144 +#: 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:84 +#: 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:80 +#: 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:134 msgid "Unable to update unknown domain." msgstr "Unable to update unknown domain." -#: src/organization/mutations/update-organization.js:141 +#: src/organization/mutations/update-organization.js:137 msgid "Unable to update unknown organization." msgstr "Unable to update unknown organization." -#: src/affiliation/mutations/update-user-role.js:132 -#: src/affiliation/mutations/update-user-role.js:157 -#: src/affiliation/mutations/update-user-role.js:252 -#: src/affiliation/mutations/update-user-role.js:263 +#: 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:70 +#: 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 +#: 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:99 -#: src/user/mutations/verify-account.js:108 +#: 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:23 +#: 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:96 -#: src/user/queries/is-user-admin.js:58 +#: 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/mutations/verify-organization.js:115 -#: src/organization/mutations/verify-organization.js:137 -#: src/organization/mutations/verify-organization.js:148 +#: 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:54 +#: src/organization/mutations/verify-organization.js:50 msgid "Unable to verify unknown organization." msgstr "Unable to verify unknown organization." @@ -1082,53 +1739,62 @@ msgstr "Unable to verify unknown organization." msgid "User could not be queried." msgstr "User could not be queried." -#: src/affiliation/mutations/update-user-role.js:273 +#: 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:75 +#: 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/tfa-required.js:15 +#: 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/verified-required.js:15 +#: 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/affiliation/loaders/load-affiliation-connections-by-org-id.js:71 +#: 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-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." +#~ 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:189 +#: 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:151 -#: src/domain/loaders/load-domain-connections-by-user-id.js:151 +#: 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." @@ -1146,26 +1812,34 @@ msgid "You must provide a `first` or `last` value to properly paginate the `Guid 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." +#~ 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:183 -#: src/organization/loaders/load-organization-connections-by-user-id.js:182 +#: 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/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." +#~ 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." @@ -1176,118 +1850,70 @@ msgstr "You must provide a `first` or `last` value to properly paginate the `Ver 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:31 -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/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/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:43 -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/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/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:236 -#: 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:198 -#: src/domain/loaders/load-domain-connections-by-user-id.js:198 -#: 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:230 -#: src/organization/loaders/load-organization-connections-by-user-id.js:229 -#: 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/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/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/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/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/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/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/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/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/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-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:210 -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:172 -#: src/domain/loaders/load-domain-connections-by-user-id.js:172 -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-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/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/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/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/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/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/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-connections-by-domain-id.js:204 -#: src/organization/loaders/load-organization-connections-by-user-id.js:203 -msgid "`{argSet}` on the `Organization` connection cannot be less than zero." -msgstr "`{argSet}` on the `Organization` connection cannot be less than zero." +#: 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/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/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/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/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/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-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/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/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/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/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 index ff354dfea6..1ef81295f7 100644 --- a/api/src/locale/fr/messages.js +++ b/api/src/locale/fr/messages.js @@ -1 +1 @@ -/*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 phase summary. Please try again.":"Impossible de charger le résumé 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 HTTPS summary. Please try again.":"Impossible de charger le résumé 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."]}}; \ No newline at end of file +/*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 index 2a8e9eb75d..94b17afd7b 100644 --- a/api/src/locale/fr/messages.po +++ b/api/src/locale/fr/messages.po @@ -11,114 +11,289 @@ msgstr "" "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 +#: 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/organization/objects/organization.js:142 +#: 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/user/mutations/sign-up.js:116 +#: 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/user/mutations/send-password-reset.js:62 +#: 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: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/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:175 +#: 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:66 +#: 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:70 -#: src/user/mutations/sign-in.js:294 +#: 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/verify-jwt.js:14 +#: src/auth/utils/verify-jwt.js:15 msgid "Invalid token, please sign in." msgstr "Jeton invalide, veuillez vous connecter." -#: src/user/mutations/reset-password.js:108 +#: 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:49 +#: 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:33 +#: 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:31 +#: 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:82 +#: 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:188 +#: 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:138 +#: 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/check-domain-ownership.js:33 -#: src/auth/check-domain-ownership.js:45 -#: src/auth/check-domain-ownership.js:67 -#: src/auth/check-domain-ownership.js:78 +#: 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:80 +#: 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-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é." +#~ 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:198 +#: 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:160 -#: src/domain/loaders/load-domain-connections-by-user-id.js:160 +#: 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é." @@ -136,26 +311,34 @@ msgid "Passing both `first` and `last` to paginate the `GuidanceTag` connection 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é." +#~ 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:192 -#: src/organization/loaders/load-organization-connections-by-user-id.js:191 +#: 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/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é." +#~ 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." @@ -166,124 +349,222 @@ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedD 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 +#: 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:168 +#: 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:134 +#: 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:102 +#: src/user/mutations/sign-up.js:99 msgid "Passwords do not match." msgstr "Les mots de passe ne correspondent pas." -#: src/organization/queries/find-organization-by-slug.js:61 -#: src/organization/queries/find-organization-by-slug.js:74 +#: 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:108 +#: 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:95 +#: 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-domain.js:110 +#: 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/organization/mutations/remove-organization.js:71 +#: 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:219 +#: 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/organization/mutations/update-organization.js:156 +#: 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/affiliation/mutations/update-user-role.js:191 -#: src/affiliation/mutations/update-user-role.js:214 -#: src/affiliation/mutations/update-user-role.js:232 -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/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:113 +#: 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:113 +#: 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:102 +#: src/domain/mutations/create-domain.js:135 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:57 +#: 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-scan.js:70 +#: 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:113 +#: src/domain/mutations/update-domain.js:162 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:97 +#: 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/create-domain.js:157 +msgid "Permission Denied: Please contact super admin for help with creating domain." +msgstr "Permission refusée : Veuillez contacter le super admin pour obtenir de l'aide pour créer le domaine." + +#: 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:85 +#: 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/organization/mutations/verify-organization.js:69 +#: 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/domain/mutations/update-domain.js:210 +msgid "Permission Denied: Please contact super admin for help with updating domain." +msgstr "Permission refusée : Veuillez contacter le super admin pour obtenir de l'aide pour mettre à jour le 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/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 +#: 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:54 +#: 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/user/mutations/remove-phone-number.js:81 +#: 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:131 +#: 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/user/mutations/update-user-profile.js:193 +#: 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/affiliation/loaders/load-affiliation-connections-by-org-id.js:103 +#: 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." @@ -296,12 +577,12 @@ msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTab 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:221 +#: 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:183 -#: src/domain/loaders/load-domain-connections-by-user-id.js:183 +#: 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." @@ -318,8 +599,12 @@ msgstr "La demande d'enregistrements `{amount}` sur la connexion `FullPassTable` 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:215 -#: src/organization/loaders/load-organization-connections-by-user-id.js:214 +#: 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." @@ -327,6 +612,10 @@ msgstr "La demande d'enregistrements `{amount}` sur la connexion `Organization` 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." @@ -337,63 +626,84 @@ msgstr "La demande d'enregistrements `{amount}` sur la connexion `VerifiedDomain 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." +#. 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/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/domain/mutations/add-organizations-domains.js:351 +#~ msgid "Successfully added {domainCount} domains to {0}." +#~ msgstr "Ajouté avec succès les domaines {domainCount} à {0}." -#: 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." +#. 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:508 +#: 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:131 +#: src/domain/mutations/request-scan.js:175 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/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/affiliation/mutations/invite-user-to-org.js:202 +#: 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é." -#: src/affiliation/mutations/leave-organization.js:444 +#. 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}" -#: src/domain/mutations/remove-domain.js:403 +#. 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}." -#: src/organization/mutations/remove-organization.js:471 +#. 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:205 +#: 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/invite-user-to-org.js:144 +#: 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." @@ -401,115 +711,187 @@ msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisatio msgid "Successfully signed out." msgstr "J'ai réussi à me déconnecter." -#: src/affiliation/mutations/transfer-org-ownership.js:224 +#. 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}" -#: src/organization/mutations/verify-organization.js:157 +#. 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:118 +#: 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/user/mutations/authenticate.js:62 +#: 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:84 +#: 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/verify-phone-number.js:63 +#: 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:50 +#: 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: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 +#: 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/user/mutations/authenticate.js:77 -#: src/user/mutations/authenticate.js:123 -#: src/user/mutations/authenticate.js:132 +#: 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/check-permission.js:30 -#: src/auth/check-permission.js:58 -#: src/auth/check-super-admin.js:20 -#: src/auth/check-super-admin.js:30 +#: 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:67 +#: 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: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 +#: 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/domain/mutations/create-domain.js:82 +#: 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:121 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:159 +#: src/domain/mutations/create-domain.js:216 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:138 -#: src/domain/mutations/create-domain.js:148 -#: src/domain/mutations/create-domain.js:197 -#: src/domain/mutations/create-domain.js:207 -#: src/domain/mutations/create-domain.js:225 -#: src/domain/mutations/create-domain.js:255 -#: src/domain/mutations/create-domain.js:273 -#: src/domain/mutations/create-domain.js:283 +#: src/domain/mutations/create-domain.js:198 +#: src/domain/mutations/create-domain.js:206 +#: src/domain/mutations/create-domain.js:238 +#: src/domain/mutations/create-domain.js:247 +#: src/domain/mutations/create-domain.js:267 +#: 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 +#: 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." +#~ 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 @@ -523,13 +905,13 @@ msgstr "Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessay #: 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." +#~ 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." +#~ 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 @@ -538,44 +920,54 @@ msgstr "Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessa #: 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." +#~ 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 +#: 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." +#~ 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 +#: 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." +#~ 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 +#: 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." +#~ 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:46 +#: src/domain/queries/find-domain-by-domain.js:41 msgid "Unable to find the requested domain." msgstr "Impossible de trouver le domaine demandé." @@ -591,28 +983,73 @@ msgstr "Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez rées 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:94 +#: 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:177 -#: src/affiliation/mutations/invite-user-to-org.js:192 +#: 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:80 +#: 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:51 +#: 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 @@ -626,13 +1063,17 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessay #: 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." +#~ 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." +#~ 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 @@ -645,17 +1086,17 @@ msgstr "Impossible de charger les données d'échec DMARC. Veuillez réessayer." 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:14 -msgid "Unable to load DMARC phase summary. Please try again." -msgstr "Impossible de charger le résumé 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." +#~ 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:477 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:489 +#: 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 @@ -663,6 +1104,45 @@ msgstr "Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer." 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." @@ -671,12 +1151,66 @@ msgstr "Impossible de charger la ou les balises d'orientation HTTPS. Veuillez r #: 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." +#~ 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." +#~ 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 @@ -691,8 +1225,12 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessaye #: 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." +#~ 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 @@ -701,66 +1239,36 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessaye #: 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:391 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:401 -#: src/domain/loaders/load-domain-connections-by-user-id.js:413 -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:533 -#: src/organization/loaders/load-organization-connections-by-domain-id.js:545 -#: src/organization/loaders/load-organization-connections-by-user-id.js:520 -#: src/organization/loaders/load-organization-connections-by-user-id.js:532 -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." +#~ 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." @@ -782,110 +1290,183 @@ msgstr "Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer 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." +#~ 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-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:403 +#: 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/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 +#: 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:123 +#: 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:80 +#: 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:126 -#: src/domain/mutations/remove-domain.js:140 -#: src/domain/mutations/remove-domain.js:179 -#: src/domain/mutations/remove-domain.js:198 +#: 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:360 -#: src/domain/mutations/remove-domain.js:383 -#: src/domain/mutations/remove-domain.js:394 +#: 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/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 +#: 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:63 -#: src/user/mutations/remove-phone-number.js:74 +#: 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:64 +#: 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:56 +#: 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:90 +#: 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:76 -msgid "Unable to remove user from organization." -msgstr "Impossible de supprimer un utilisateur 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:110 -#: src/affiliation/mutations/remove-user-from-org.js:137 -#: src/affiliation/mutations/remove-user-from-org.js:181 -#: src/affiliation/mutations/remove-user-from-org.js:194 +#: 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:62 +#: 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:57 +#: 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/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 +#: 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:161 -#: src/domain/objects/domain.js:207 +#: 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}" @@ -902,179 +1483,249 @@ msgstr "Impossible d'envoyer l'email d'authentification. Veuillez réessayer." 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:31 -#: src/notify/notify-send-org-invite-email.js:26 +#: 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-password-reset-email.js:30 +#: 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." +#~ 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:30 +#: 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:108 -#: src/user/mutations/set-phone-number.js:117 +#: 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: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:275 -#: src/user/mutations/sign-in.js:284 +#: 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:204 -#: src/user/mutations/sign-up.js:218 +#: 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:172 -#: src/user/mutations/sign-up.js:182 -#: src/user/mutations/sign-up.js:240 -#: src/user/mutations/sign-up.js:250 +#: 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/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 +#: 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: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: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:112 +#: 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:65 +#: 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:144 +#: 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:92 -#: src/user/mutations/verify-phone-number.js:103 +#: 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/update-domain.js:94 +#: 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:284 +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:148 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:142 +#: src/domain/mutations/update-domain.js:189 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:131 -#: src/domain/mutations/update-domain.js:178 -#: src/domain/mutations/update-domain.js:188 +#: src/domain/mutations/update-domain.js:179 +#: src/domain/mutations/update-domain.js:241 +#: src/domain/mutations/update-domain.js:295 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 +#: 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:62 +#: 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:76 +#: 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:90 +#: 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:119 -#: src/user/mutations/update-user-password.js:128 +#: 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:167 -#: src/user/mutations/update-user-profile.js:176 +#: 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:98 +#: 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:144 +#: 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:84 +#: 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:80 +#: 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:134 msgid "Unable to update unknown domain." msgstr "Impossible de mettre à jour un domaine inconnu." -#: src/organization/mutations/update-organization.js:141 +#: src/organization/mutations/update-organization.js:137 msgid "Unable to update unknown organization." msgstr "Impossible de mettre à jour une organisation inconnue." -#: src/affiliation/mutations/update-user-role.js:132 -#: src/affiliation/mutations/update-user-role.js:157 -#: src/affiliation/mutations/update-user-role.js:252 -#: src/affiliation/mutations/update-user-role.js:263 +#: 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:70 +#: 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 +#: 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:99 -#: src/user/mutations/verify-account.js:108 +#: 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:23 +#: 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:96 -#: src/user/queries/is-user-admin.js:58 +#: 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/mutations/verify-organization.js:115 -#: src/organization/mutations/verify-organization.js:137 -#: src/organization/mutations/verify-organization.js:148 +#: 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:54 +#: src/organization/mutations/verify-organization.js:50 msgid "Unable to verify unknown organization." msgstr "Impossible de vérifier une organisation inconnue." @@ -1082,53 +1733,62 @@ msgstr "Impossible de vérifier une organisation inconnue." msgid "User could not be queried." msgstr "L'utilisateur n'a pas pu être interrogé." -#: src/affiliation/mutations/update-user-role.js:273 +#: 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:75 +#: 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/tfa-required.js:15 +#: 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/verified-required.js:15 +#: 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/affiliation/loaders/load-affiliation-connections-by-org-id.js:71 +#: 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-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`." +#~ 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:189 +#: 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:151 -#: src/domain/loaders/load-domain-connections-by-user-id.js:151 +#: 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`." @@ -1146,26 +1806,34 @@ msgid "You must provide a `first` or `last` value to properly paginate the `Guid 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`." +#~ 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:183 -#: src/organization/loaders/load-organization-connections-by-user-id.js:182 +#: 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/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`." +#~ 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." @@ -1176,118 +1844,70 @@ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctemen 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:31 -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/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/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:43 -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/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/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:236 -#: 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:198 -#: src/domain/loaders/load-domain-connections-by-user-id.js:198 -#: 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:230 -#: src/organization/loaders/load-organization-connections-by-user-id.js:229 -#: 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/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/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/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/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/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/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/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/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/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-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:210 -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:172 -#: src/domain/loaders/load-domain-connections-by-user-id.js:172 -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-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/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/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/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/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/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/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-connections-by-domain-id.js:204 -#: src/organization/loaders/load-organization-connections-by-user-id.js:203 -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/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/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/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/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/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/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-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/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/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/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/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/src/mutation.js b/api/src/mutation.js index 632ec995a0..2bdcb1ddff 100644 --- a/api/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 index 236f3c9d75..f5cac5713d 100644 --- a/api/src/node.js +++ b/api/src/node.js @@ -1,7 +1,8 @@ -import { nodeDefinitions } from 'graphql-relay' +import {nodeDefinitions} from 'graphql-relay' -export const { nodeField, nodesField, nodeInterface } = nodeDefinitions( - (_globalId) => {}, +export const {nodeField, nodesField, nodeInterface} = nodeDefinitions( + (_globalId) => { + }, (object) => { switch (object) { default: diff --git a/api/src/notify/__tests__/notify-send-authenticate-email.test.js b/api/src/notify/__tests__/notify-send-authenticate-email.test.js index 5a88942a24..0fc651258f 100644 --- a/api/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/src/notify/__tests__/notify-send-authenticate-text-msg.test.js b/api/src/notify/__tests__/notify-send-authenticate-text-msg.test.js index d306674978..ef6c113864 100644 --- a/api/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/src/notify/__tests__/notify-send-org-invite-create-account.test.js b/api/src/notify/__tests__/notify-send-org-invite-create-account.test.js index 7e2beb447b..feccc6e333 100644 --- a/api/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 index 13ddd5f129..59dabcc567 100644 --- a/api/src/notify/__tests__/notify-send-org-invite-email.test.js +++ b/api/src/notify/__tests__/notify-send-org-invite-email.test.js @@ -4,7 +4,7 @@ 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 +const { NOTIFICATION_ORG_INVITE_BILINGUAL } = process.env describe('given the sendOrgInviteEmail function', () => { let i18n @@ -55,7 +55,6 @@ describe('given the sendOrgInviteEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'english', } const mockedSendOrgInviteEmail = sendOrgInviteEmail({ @@ -64,26 +63,22 @@ describe('given the sendOrgInviteEmail function', () => { }) await mockedSendOrgInviteEmail({ user, - orgName: 'Test Org', + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', }) - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_EN, - user.userName, - { - personalisation: { - display_name: user.displayName, - organization_name: 'Test Org', - }, + 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 sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -91,7 +86,6 @@ describe('given the sendOrgInviteEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'english', } try { @@ -101,12 +95,11 @@ describe('given the sendOrgInviteEmail function', () => { }) await mockedSendOrgInviteEmail({ user, - orgName: 'Test Org', + 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(err).toEqual(new Error('Unable to send org invite email. Please try again.')) } expect(consoleOutput).toEqual([ @@ -140,7 +133,6 @@ describe('given the sendOrgInviteEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'french', } const mockedSendOrgInviteEmail = sendOrgInviteEmail({ @@ -149,26 +141,22 @@ describe('given the sendOrgInviteEmail function', () => { }) await mockedSendOrgInviteEmail({ user, - orgName: 'Test Org', + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', }) - expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_FR, - user.userName, - { - personalisation: { - display_name: user.displayName, - organization_name: 'Test Org', - }, + 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 sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -176,7 +164,6 @@ describe('given the sendOrgInviteEmail function', () => { const user = { userName: 'test@email.ca', displayName: 'Test Account', - preferredLang: 'french', } try { @@ -186,14 +173,11 @@ describe('given the sendOrgInviteEmail function', () => { }) await mockedSendOrgInviteEmail({ user, - orgName: 'Test Org', + 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(err).toEqual(new Error("Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api/src/notify/__tests__/notify-send-password-reset-email.test.js b/api/src/notify/__tests__/notify-send-password-reset-email.test.js index 4197c5cc08..b906d15d63 100644 --- a/api/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/__tests__/notify-send-tfa-text-msg.test.js b/api/src/notify/__tests__/notify-send-tfa-text-msg.test.js deleted file mode 100644 index be27ec56aa..0000000000 --- a/api/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/src/notify/__tests__/notify-send-verification-email.test.js b/api/src/notify/__tests__/notify-send-verification-email.test.js deleted file mode 100644 index 3d7522bf9f..0000000000 --- a/api/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/src/notify/index.js b/api/src/notify/index.js index fc86b60b02..9e306a9a0a 100644 --- a/api/src/notify/index.js +++ b/api/src/notify/index.js @@ -1,8 +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-tfa-text-msg' +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 index 098ca95800..b20b762923 100644 --- a/api/src/notify/notify-client.js +++ b/api/src/notify/notify-client.js @@ -1,6 +1,6 @@ -import { NotifyClient } from 'notifications-node-client' +import {NotifyClient} from 'notifications-node-client' -const { NOTIFICATION_API_KEY, NOTIFICATION_API_URL } = process.env +const {NOTIFICATION_API_KEY, NOTIFICATION_API_URL} = process.env export const notifyClient = new NotifyClient( NOTIFICATION_API_URL, diff --git a/api/src/notify/notify-send-authenticate-email.js b/api/src/notify/notify-send-authenticate-email.js index 2973cf314e..08b9c26c94 100644 --- a/api/src/notify/notify-send-authenticate-email.js +++ b/api/src/notify/notify-send-authenticate-email.js @@ -1,8 +1,8 @@ -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -const { NOTIFICATION_AUTHENTICATE_EMAIL_ID } = process.env +const {NOTIFICATION_AUTHENTICATE_EMAIL_ID} = process.env -export const sendAuthEmail = ({ notifyClient, i18n }) => async ({ user }) => { +export const sendAuthEmail = ({notifyClient, i18n}) => async ({user}) => { const templateId = NOTIFICATION_AUTHENTICATE_EMAIL_ID try { await notifyClient.sendEmail(templateId, user.userName, { diff --git a/api/src/notify/notify-send-authenticate-text-msg.js b/api/src/notify/notify-send-authenticate-text-msg.js index c476efd3bc..af9e3a93cf 100644 --- a/api/src/notify/notify-send-authenticate-text-msg.js +++ b/api/src/notify/notify-send-authenticate-text-msg.js @@ -1,17 +1,17 @@ import crypto from 'crypto' -import { t } from '@lingui/macro' +import {t} from '@lingui/macro' -const { CIPHER_KEY, NOTIFICATION_AUTHENTICATE_TEXT_ID } = process.env +const {CIPHER_KEY, NOTIFICATION_AUTHENTICATE_TEXT_ID} = process.env -export const sendAuthTextMsg = ({ notifyClient, i18n }) => async ({ user }) => { +export const sendAuthTextMsg = ({notifyClient, i18n}) => async ({user}) => { const templateId = NOTIFICATION_AUTHENTICATE_TEXT_ID - const { iv, tag, phoneNumber: encryptedData } = user.phoneDetails + const {iv, tag, phoneNumber: encryptedData} = user.phoneDetails const decipher = crypto.createDecipheriv( 'aes-256-ccm', String(CIPHER_KEY), Buffer.from(iv, 'hex'), - { authTagLength: 16 }, + {authTagLength: 16}, ) decipher.setAuthTag(Buffer.from(tag, 'hex')) let phoneNumber = decipher.update(encryptedData, 'hex', 'utf8') 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 index 40c7361240..9fb2e9aff3 100644 --- a/api/src/notify/notify-send-org-invite-create-account.js +++ b/api/src/notify/notify-send-org-invite-create-account.js @@ -1,34 +1,21 @@ import { t } from '@lingui/macro' -const { - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN, - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR, -} = process.env +const { NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL } = 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 +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.`)) + } } - - 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/src/notify/notify-send-org-invite-email.js b/api/src/notify/notify-send-org-invite-email.js index 3f68106fce..84e80b021e 100644 --- a/api/src/notify/notify-send-org-invite-email.js +++ b/api/src/notify/notify-send-org-invite-email.js @@ -1,29 +1,20 @@ import { t } from '@lingui/macro' -const { NOTIFICATION_ORG_INVITE_EN, NOTIFICATION_ORG_INVITE_FR } = process.env +const { NOTIFICATION_ORG_INVITE_BILINGUAL } = 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 +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.`)) + } } - - 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/src/notify/notify-send-password-reset-email.js b/api/src/notify/notify-send-password-reset-email.js index b08727593d..8901108dfb 100644 --- a/api/src/notify/notify-send-password-reset-email.js +++ b/api/src/notify/notify-send-password-reset-email.js @@ -1,33 +1,20 @@ import { t } from '@lingui/macro' -const { - NOTIFICATION_PASSWORD_RESET_EN, - NOTIFICATION_PASSWORD_RESET_FR, -} = process.env +const { NOTIFICATION_PASSWORD_RESET_BILINGUAL } = 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 +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.`)) + } } - - 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/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-tfa-text-msg.js b/api/src/notify/notify-send-tfa-text-msg.js deleted file mode 100644 index 75013b3dca..0000000000 --- a/api/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/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 index cba13bd00d..33ec8c43dc 100644 --- a/api/src/notify/notify-send-verification-email.js +++ b/api/src/notify/notify-send-verification-email.js @@ -1,33 +1,20 @@ import { t } from '@lingui/macro' -const { - NOTIFICATION_VERIFICATION_EMAIL_EN, - NOTIFICATION_VERIFICATION_EMAIL_FR, -} = process.env +const { NOTIFICATION_VERIFICATION_EMAIL_BILINGUAL } = 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 +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.`)) + } } - - 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/src/organization/data-source.js b/api/src/organization/data-source.js new file mode 100644 index 0000000000..7484037642 --- /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: null + }, + 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 index e170187ec3..9366445c50 100644 --- a/api/src/organization/index.js +++ b/api/src/organization/index.js @@ -1,3 +1,4 @@ +export * from './data-source' export * from './inputs' export * from './loaders' export * from './mutations' diff --git a/api/src/organization/inputs/__tests__/organization-order.test.js b/api/src/organization/inputs/__tests__/organization-order.test.js index f987ff0ae7..257ef39b29 100644 --- a/api/src/organization/inputs/__tests__/organization-order.test.js +++ b/api/src/organization/inputs/__tests__/organization-order.test.js @@ -9,17 +9,13 @@ describe('given the organizationOrder input object', () => { const demoType = organizationOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(OrganizationOrderField), - ) + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(OrganizationOrderField)) }) }) }) diff --git a/api/src/organization/inputs/organization-order.js b/api/src/organization/inputs/organization-order.js index 1c310dfdf5..56a885a4a8 100644 --- a/api/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/src/organization/loaders/__tests__/load-organization-by-key.test.js b/api/src/organization/loaders/__tests__/load-organization-by-key.test.js index 6003e9f1a4..d4ec290547 100644 --- a/api/src/organization/loaders/__tests__/load-organization-by-key.test.js +++ b/api/src/organization/loaders/__tests__/load-organization-by-key.test.js @@ -1,4 +1,5 @@ -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' @@ -9,7 +10,7 @@ import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given a loadOrgByKey dataloader', () => { - let query, drop, truncate, collections, i18n + let query, drop, truncate, collections, db, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -22,33 +23,22 @@ describe('given a loadOrgByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { - await collections.organizations.save({ - verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, + const org1 = await collections.organizations.save({ + verified: false, + externalId: 'test', orgDetails: { en: { slug: 'communications-security-establishment', @@ -72,20 +62,16 @@ describe('given a loadOrgByKey dataloader', () => { }, }, }) - await collections.organizations.save({ - verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, + const summary1 = await collections.organizationSummaries.save({ + organization: org1._id, + web: { pass: 50, fail: 1000, total: 1050 }, + mail: { pass: 50, fail: 1000, total: 1050 }, + }) + await db.collection('organizations').update(org1._key, { latestSummaryId: summary1._id }) + + const org2 = await collections.organizations.save({ + verified: false, + externalId: 'test', orgDetails: { en: { slug: 'treasury-board-secretariat', @@ -109,6 +95,12 @@ describe('given a loadOrgByKey dataloader', () => { }, }, }) + const summary2 = await collections.organizationSummaries.save({ + organization: org2._id, + web: { pass: 50, fail: 1000, total: 1050 }, + mail: { pass: 50, fail: 1000, total: 1050 }, + }) + await db.collection('organizations').update(org2._key, { latestSummaryId: summary2._id }) }) afterEach(async () => { await truncate() @@ -138,7 +130,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.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("en", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -155,7 +147,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.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("en", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -192,7 +184,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.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("fr", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -209,7 +201,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.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("fr", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -243,9 +235,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', @@ -256,9 +246,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([ @@ -284,9 +272,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([ @@ -312,9 +298,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', @@ -325,11 +309,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([ @@ -355,11 +335,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/src/organization/loaders/__tests__/load-organization-by-slug.test.js b/api/src/organization/loaders/__tests__/load-organization-by-slug.test.js index b34b2c9daf..65f1581ab0 100644 --- a/api/src/organization/loaders/__tests__/load-organization-by-slug.test.js +++ b/api/src/organization/loaders/__tests__/load-organization-by-slug.test.js @@ -1,4 +1,5 @@ -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' @@ -9,7 +10,7 @@ import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given a loadOrgBySlug dataloader', () => { - let query, drop, truncate, collections, i18n + let query, drop, truncate, collections, db, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -22,33 +23,22 @@ describe('given a loadOrgBySlug dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { - await collections.organizations.save({ - verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, + const org1 = await collections.organizations.save({ + verified: false, + externalId: 'test', orgDetails: { en: { slug: 'communications-security-establishment', @@ -72,8 +62,23 @@ describe('given a loadOrgBySlug dataloader', () => { }, }, }) + const summary1 = await collections.organizationSummaries.save({ + organization: org1._id, + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }) + await db.collection('organizations').update(org1._key, { latestSummaryId: summary1._id }) await collections.organizations.save({ - verified: true, + verified: false, + externalId: 'test', orgDetails: { en: { slug: 'treasury-board-secretariat', @@ -134,8 +139,9 @@ describe('given a loadOrgBySlug dataloader', () => { _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(domains), - summaries: org.summaries, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null, slugEN: org.orgDetails.en.slug, slugFR: org.orgDetails.fr.slug }, @@ -166,7 +172,7 @@ describe('given a loadOrgBySlug dataloader', () => { id: org._key, verified: org.verified, domainCount: COUNT(domains), - summaries: org.summaries, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null, slugEN: org.orgDetails.en.slug, slugFR: org.orgDetails.fr.slug }, @@ -216,11 +222,12 @@ describe('given a loadOrgBySlug dataloader', () => { _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(domains), - summaries: org.summaries, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null, slugEN: org.orgDetails.en.slug, slugFR: org.orgDetails.fr.slug - }, + }, TRANSLATE("fr", org.orgDetails) ) ` @@ -248,10 +255,10 @@ describe('given a loadOrgBySlug dataloader', () => { id: org._key, verified: org.verified, domainCount: COUNT(domains), - summaries: org.summaries, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null, slugEN: org.orgDetails.en.slug, slugFR: org.orgDetails.fr.slug - }, + }, TRANSLATE("fr", org.orgDetails) ) ` @@ -287,9 +294,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', @@ -300,9 +305,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([ @@ -328,9 +331,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([ @@ -356,9 +357,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', @@ -369,11 +368,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([ @@ -399,11 +394,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/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 index 7a930dfcaf..1e7ef4c31f 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,17 +13,7 @@ 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, db, user, org, orgTwo, domain, domainTwo, domainThree, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -37,40 +28,28 @@ describe('given the load organizations connection function', () => { 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, - })) + ;({ query, drop, truncate, collections, db } = 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', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) org = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-one', @@ -94,20 +73,23 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgSummary = await collections.organizationSummaries.save({ + organization: org._id, + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }) + await db.collection("organizations").update(org._key, { latestSummaryId: orgSummary._id }) orgTwo = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-two', @@ -131,6 +113,20 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + web: { + pass: 52, + fail: 1002, + total: 1054, + }, + mail: { + pass: 52, + fail: 1002, + total: 1054, + }, + }) + await db.collection("organizations").update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) await collections.affiliations.save({ _from: org._id, _to: user._id, @@ -201,10 +197,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -251,10 +244,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -301,10 +291,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -350,10 +337,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -496,10 +480,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -543,18 +524,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-three', @@ -578,6 +548,20 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + web: { + pass: 51, + fail: 1001, + total: 1052, + }, + mail: { + pass: 51, + fail: 1001, + total: 1052, + }, + }) + await db.collection("organizations").update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) await collections.affiliations.save({ _from: orgThree._id, _to: user._id, @@ -2206,10 +2190,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -2262,10 +2243,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -2302,18 +2280,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'super-admin', @@ -2337,6 +2304,20 @@ describe('given the load organizations connection function', () => { }, }, }) + const saOrgSummary = await collections.organizationSummaries.save({ + organization: saOrg._id, + web: { + pass: 52, + fail: 1002, + total: 1054, + }, + mail: { + pass: 52, + fail: 1002, + total: 1054, + }, + }) + await db.collection("organizations").update(saOrg._key, { latestSummaryId: saOrgSummary._id }) await collections.affiliations.save({ _from: saOrg._id, _to: user._id, @@ -2359,11 +2340,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -2419,11 +2396,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -2492,10 +2465,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -2542,10 +2512,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -2592,10 +2559,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -2641,10 +2605,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -2787,10 +2748,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -2834,18 +2792,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-three', @@ -2869,6 +2816,20 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + web: { + pass: 51, + fail: 1001, + total: 1052, + }, + mail: { + pass: 51, + fail: 1001, + total: 1052, + }, + }) + await db.collection("organizations").update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) await collections.affiliations.save({ _from: orgThree._id, _to: user._id, @@ -4497,10 +4458,7 @@ describe('given the load organizations connection function', () => { }) 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 @@ -4553,10 +4511,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -4593,18 +4548,7 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'super-admin', @@ -4628,6 +4572,20 @@ describe('given the load organizations connection function', () => { }, }, }) + const saOrgSummary = await collections.organizationSummaries.save({ + organization: saOrg._id, + web: { + pass: 52, + fail: 1002, + total: 1054, + }, + mail: { + pass: 52, + fail: 1002, + total: 1054, + }, + }) + await db.collection("organizations").update(saOrg._key, { latestSummaryId: saOrgSummary._id }) await collections.affiliations.save({ _from: saOrg._id, _to: user._id, @@ -4650,11 +4608,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -4710,11 +4664,7 @@ describe('given the load organizations connection function', () => { }) 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, @@ -4857,11 +4807,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([ @@ -4889,11 +4835,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([ @@ -4971,9 +4913,7 @@ 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', @@ -4992,11 +4932,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: ${ @@ -5008,9 +4944,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 = loadOrgConnectionsByDomainId({ query, language: 'en', @@ -5029,11 +4963,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: ${ @@ -5048,9 +4978,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, @@ -5070,9 +4998,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([ @@ -5108,11 +5034,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([ @@ -5224,9 +5146,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.'), ) } @@ -5255,11 +5175,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([ @@ -5337,9 +5253,7 @@ 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', @@ -5359,9 +5273,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([ @@ -5374,9 +5286,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 = loadOrgConnectionsByDomainId({ query, language: 'fr', @@ -5396,9 +5306,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([ @@ -5414,9 +5322,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, @@ -5436,11 +5342,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([ @@ -5476,11 +5378,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/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 index d8c2b6a41e..8d2380c3e6 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,17 +13,7 @@ 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, db, user, orgOne, orgTwo, i18n, domain, domainTwo, domainThree const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -38,40 +29,28 @@ describe('given the load organization connections by user id function', () => { 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, - })) + ;({ query, drop, truncate, collections, db } = 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', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) orgOne = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-one', @@ -95,20 +74,23 @@ describe('given the load organization connections by user id function', () => { }, }, }) + const orgOneSummary = await collections.organizationSummaries.save({ + organization: orgOne._id, + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }) + await db.collection("organizations").update(orgOne._key, { latestSummaryId: orgOneSummary._id }) orgTwo = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-two', @@ -132,6 +114,20 @@ describe('given the load organization connections by user id function', () => { }, }, }) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + web: { + pass: 52, + fail: 1002, + total: 1054, + }, + mail: { + pass: 52, + fail: 1002, + total: 1054, + }, + }) + await db.collection("organizations").update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) await collections.affiliations.save({ _from: orgOne._id, _to: user._id, @@ -203,10 +199,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -250,10 +243,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -297,10 +287,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -343,10 +330,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -481,10 +465,7 @@ 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, @@ -522,10 +503,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), }, } @@ -539,18 +517,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-three', @@ -574,6 +541,20 @@ describe('given the load organization connections by user id function', () => { }, }, }) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + web: { + pass: 51, + fail: 1001, + total: 1052, + }, + mail: { + pass: 51, + fail: 1001, + total: 1052, + }, + }) + await db.collection("organizations").update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) await collections.affiliations.save({ _from: orgThree._id, _to: user._id, @@ -2125,6 +2106,56 @@ 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({ @@ -2143,10 +2174,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 @@ -2190,10 +2218,7 @@ describe('given the load organization connections by user id function', () => { }) 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, @@ -2227,18 +2252,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'super-admin', @@ -2262,6 +2276,20 @@ describe('given the load organization connections by user id function', () => { }, }, }) + const saOrgSummary = await collections.organizationSummaries.save({ + organization: saOrg._id, + web: { + pass: 52, + fail: 1002, + total: 1054, + }, + mail: { + pass: 52, + fail: 1002, + total: 1054, + }, + }) + await db.collection("organizations").update(saOrg._key, { latestSummaryId: saOrgSummary._id }) await collections.affiliations.save({ _from: saOrg._id, _to: user._id, @@ -2280,11 +2308,7 @@ describe('given the load organization connections by user id function', () => { }) 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, @@ -2317,10 +2341,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[2]._key), }, } @@ -2340,11 +2361,7 @@ describe('given the load organization connections by user id function', () => { }) 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, @@ -2371,10 +2388,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), }, } @@ -2447,10 +2461,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -2494,10 +2505,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -2541,10 +2549,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -2587,10 +2592,7 @@ describe('given the load organization connections by user id function', () => { }) 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 @@ -2626,18 +2628,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'slug-org-three', @@ -2661,6 +2652,20 @@ describe('given the load organization connections by user id function', () => { }, }, }) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + web: { + pass: 51, + fail: 1001, + total: 1052, + }, + mail: { + pass: 51, + fail: 1001, + total: 1052, + }, + }) + await db.collection("organizations").update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) await collections.affiliations.save({ _from: orgThree._id, _to: user._id, @@ -4316,10 +4321,7 @@ 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, @@ -4357,10 +4359,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), }, } @@ -4387,10 +4386,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 @@ -4434,10 +4430,7 @@ describe('given the load organization connections by user id function', () => { }) 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, @@ -4471,18 +4464,7 @@ describe('given the load organization connections by user id function', () => { beforeEach(async () => { saOrg = await collections.organizations.save({ verified: false, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, + externalId: 'test', orgDetails: { en: { slug: 'super-admin', @@ -4506,6 +4488,20 @@ describe('given the load organization connections by user id function', () => { }, }, }) + const saOrgSummary = await collections.organizationSummaries.save({ + organization: saOrg._id, + web: { + pass: 52, + fail: 1002, + total: 1054, + }, + mail: { + pass: 52, + fail: 1002, + total: 1054, + }, + }) + await db.collection("organizations").update(saOrg._key, { latestSummaryId: saOrgSummary._id }) await collections.affiliations.save({ _from: saOrg._id, _to: user._id, @@ -4524,11 +4520,7 @@ describe('given the load organization connections by user id function', () => { }) 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, @@ -4561,10 +4553,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[2]._key), }, } @@ -4584,11 +4573,7 @@ describe('given the load organization connections by user id function', () => { }) 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, @@ -4615,10 +4600,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), }, } @@ -4761,11 +4743,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([ @@ -4792,11 +4770,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([ @@ -4872,9 +4846,7 @@ 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, @@ -4893,11 +4865,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.`, @@ -4907,9 +4875,7 @@ 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, @@ -4928,11 +4894,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.`, @@ -4945,11 +4907,7 @@ 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, @@ -4968,9 +4926,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([ @@ -4984,9 +4940,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) @@ -5008,9 +4962,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([ @@ -5118,9 +5070,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.'), ) } @@ -5148,11 +5098,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([ @@ -5228,9 +5174,7 @@ 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, @@ -5250,9 +5194,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([ @@ -5263,9 +5205,7 @@ 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, @@ -5285,9 +5225,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([ @@ -5301,11 +5239,7 @@ 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, @@ -5324,11 +5258,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([ @@ -5342,9 +5272,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) @@ -5366,11 +5294,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 index 4c2aee2990..82b0be23f5 100644 --- a/api/src/organization/loaders/index.js +++ b/api/src/organization/loaders/index.js @@ -2,3 +2,7 @@ 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 index 8ab995c432..13ac3a803f 100644 --- a/api/src/organization/loaders/load-organization-by-key.js +++ b/api/src/organization/loaders/load-organization-by-key.js @@ -19,19 +19,16 @@ export const loadOrgByKey = ({ query, language, userKey, i18n }) => _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(orgDomains), - summaries: org.summaries - }, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null + }, 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.`), - ) + 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 = {} @@ -40,12 +37,8 @@ export const loadOrgByKey = ({ query, language, userKey, i18n }) => 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.`), - ) + 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 index 74daddc9ae..e895a4e803 100644 --- a/api/src/organization/loaders/load-organization-by-slug.js +++ b/api/src/organization/loaders/load-organization-by-slug.js @@ -20,8 +20,9 @@ export const loadOrgBySlug = ({ query, language, userKey, i18n }) => _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(orgDomains), - summaries: org.summaries, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null, slugEN: org.orgDetails.en.slug, slugFR: org.orgDetails.fr.slug }, @@ -29,12 +30,8 @@ export const loadOrgBySlug = ({ query, language, userKey, i18n }) => ) ` } 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.`), - ) + 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 = {} @@ -44,12 +41,8 @@ export const loadOrgBySlug = ({ query, language, userKey, i18n }) => 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.`), - ) + 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/src/organization/loaders/load-organization-connections-by-domain-id.js b/api/src/organization/loaders/load-organization-connections-by-domain-id.js index baf06b9c0f..49653adee5 100644 --- a/api/src/organization/loaders/load-organization-connections-by-domain-id.js +++ b/api/src/organization/loaders/load-organization-connections-by-domain-id.js @@ -2,395 +2,363 @@ import { aql } from 'arangojs' import { fromGlobalId, toGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' -export const loadOrgConnectionsByDomainId = ({ - query, - language, - userKey, - cleanseInput, - i18n, - auth: { loginRequiredBool }, -}) => async ({ - domainId, - after, - before, - first, - last, - orderBy, - search, - isSuperAdmin, - isAdmin, - includeSuperAdminOrg, -}) => { - const userDBId = `users/${userKey}` +export const loadOrgConnectionsByDomainId = + ({ 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`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(org._key) > TO_NUMBER(${afterId})` + 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(org._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`<` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } + + afterVar = aql`LET afterVar = DOCUMENT(organizations, ${afterId})` + + let documentField = aql`` + let orgField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'acronym') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).acronym` + orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + } else if (orderBy.field === 'name') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).name` + orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + } else if (orderBy.field === 'slug') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).slug` + orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + } else if (orderBy.field === 'zone') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).zone` + orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + } else if (orderBy.field === 'sector') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).sector` + orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + } else if (orderBy.field === 'country') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).country` + orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + } else if (orderBy.field === 'province') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).province` + orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + } else if (orderBy.field === 'city') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).city` + orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + } else if (orderBy.field === 'verified') { + documentField = aql`afterVar.verified` + orgField = aql`org.verified` + } else if (orderBy.field === 'summary-mail-pass') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` + } else if (orderBy.field === 'summary-mail-fail') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` + } else if (orderBy.field === 'summary-mail-total') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` + } else if (orderBy.field === 'summary-web-pass') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` + } else if (orderBy.field === 'summary-web-fail') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` + } else if (orderBy.field === 'summary-web-total') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` + } else if (orderBy.field === 'domain-count') { + documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` + orgField = aql`COUNT(orgDomains)` + } + + afterTemplate = aql` + FILTER ${orgField} ${afterTemplateDirection} ${documentField} + OR (${orgField} == ${documentField} + AND TO_NUMBER(org._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeVar = aql`` + let beforeTemplate = aql`` + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`>` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(organizations, ${beforeId})` + + let documentField = aql`` + let orgField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'acronym') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).acronym` + orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + } else if (orderBy.field === 'name') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).name` + orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + } else if (orderBy.field === 'slug') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).slug` + orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + } else if (orderBy.field === 'zone') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).zone` + orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + } else if (orderBy.field === 'sector') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).sector` + orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + } else if (orderBy.field === 'country') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).country` + orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + } else if (orderBy.field === 'province') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).province` + orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + } else if (orderBy.field === 'city') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).city` + orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + } else if (orderBy.field === 'verified') { + documentField = aql`beforeVar.verified` + orgField = aql`org.verified` + } else if (orderBy.field === 'summary-mail-pass') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` + } else if (orderBy.field === 'summary-mail-fail') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` + } else if (orderBy.field === 'summary-mail-total') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` + } else if (orderBy.field === 'summary-web-pass') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` + } else if (orderBy.field === 'summary-web-fail') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` + } else if (orderBy.field === 'summary-web-total') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` + } else if (orderBy.field === 'domain-count') { + documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` + orgField = aql`COUNT(orgDomains)` + } + + beforeTemplate = aql` + FILTER ${orgField} ${beforeTemplateDirection} ${documentField} + OR (${orgField} == ${documentField} + AND TO_NUMBER(org._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: loadOrgConnectionsByDomainId.`, + ) + throw new Error( + 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.`), + ) + } 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: loadOrgConnectionsByDomainId.`, + ) + 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.`) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(org._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(org._key) DESC LIMIT TO_NUMBER(${last})` + } } else { - let afterTemplateDirection = aql`<` + 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: loadOrgConnectionsByDomainId.`, + ) + 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)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`<` + let hasPreviousPageDirection = aql`>` if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` } - afterVar = aql`LET afterVar = DOCUMENT(organizations, ${afterId})` - - let documentField = aql`` let orgField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` /* istanbul ignore else */ if (orderBy.field === 'acronym') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).acronym` orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).acronym` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).acronym` } else if (orderBy.field === 'name') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).name` orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).name` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).name` } else if (orderBy.field === 'slug') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).slug` orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).slug` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).slug` } else if (orderBy.field === 'zone') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).zone` orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).zone` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).zone` } else if (orderBy.field === 'sector') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).sector` orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).sector` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).sector` } else if (orderBy.field === 'country') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).country` orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).country` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).country` } else if (orderBy.field === 'province') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).province` orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).province` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).province` } else if (orderBy.field === 'city') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).city` orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).city` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).city` } else if (orderBy.field === 'verified') { - documentField = aql`afterVar.verified` orgField = aql`org.verified` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`afterVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`afterVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`afterVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`afterVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`afterVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`afterVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` } else if (orderBy.field === 'domain-count') { - documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` orgField = aql`COUNT(orgDomains)` + hasNextPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND LAST(retrievedOrgs)._id claims RETURN e._to)` + hasPreviousPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND FIRST(retrievedOrgs)._id claims RETURN e._to)` } - afterTemplate = aql` - FILTER ${orgField} ${afterTemplateDirection} ${documentField} - OR (${orgField} == ${documentField} - AND TO_NUMBER(org._key) > TO_NUMBER(${afterId})) - ` + hasNextPageFilter = aql` + FILTER ${orgField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${orgField} == ${hasNextPageDocumentField} + AND TO_NUMBER(org._key) > TO_NUMBER(FIRST(retrievedOrgs)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${orgField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${orgField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)) + ` } - } - let beforeVar = aql`` - let beforeTemplate = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`>` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(organizations, ${beforeId})` - - let documentField = aql`` - let orgField = aql`` + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { /* istanbul ignore else */ if (orderBy.field === 'acronym') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).acronym` - orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).acronym ${orderBy.direction},` } else if (orderBy.field === 'name') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).name` - orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).name ${orderBy.direction},` } else if (orderBy.field === 'slug') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).slug` - orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).slug ${orderBy.direction},` } else if (orderBy.field === 'zone') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).zone` - orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).zone ${orderBy.direction},` } else if (orderBy.field === 'sector') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).sector` - orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).sector ${orderBy.direction},` } else if (orderBy.field === 'country') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).country` - orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).country ${orderBy.direction},` } else if (orderBy.field === 'province') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).province` - orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).province ${orderBy.direction},` } else if (orderBy.field === 'city') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).city` - orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).city ${orderBy.direction},` } else if (orderBy.field === 'verified') { - documentField = aql`beforeVar.verified` - orgField = aql`org.verified` + sortByField = aql`org.verified ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`beforeVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`beforeVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`beforeVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.total ${orderBy.direction},` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`beforeVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`beforeVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`beforeVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.total ${orderBy.direction},` } else if (orderBy.field === 'domain-count') { - documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` - orgField = aql`COUNT(orgDomains)` + sortByField = aql`COUNT(orgDomains) ${orderBy.direction},` } - - beforeTemplate = aql` - FILTER ${orgField} ${beforeTemplateDirection} ${documentField} - OR (${orgField} == ${documentField} - AND TO_NUMBER(org._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: loadOrgConnectionsByDomainId.`, - ) - throw new Error( - 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.`, - ), - ) - } 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: loadOrgConnectionsByDomainId.`, - ) - 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.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(org._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(org._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: loadOrgConnectionsByDomainId.`, - ) - 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)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`<` - let hasPreviousPageDirection = aql`>` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } - - let orgField = aql`` - let hasNextPageDocumentField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'acronym') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).acronym` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).acronym` - } else if (orderBy.field === 'name') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).name` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).name` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).name` - } else if (orderBy.field === 'slug') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).slug` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).slug` - } else if (orderBy.field === 'zone') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).zone` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).zone` - } else if (orderBy.field === 'sector') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).sector` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).sector` - } else if (orderBy.field === 'country') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).country` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).country` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).country` - } else if (orderBy.field === 'province') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).province` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).province` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).province` - } else if (orderBy.field === 'city') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).city` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).city` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).city` - } else if (orderBy.field === 'verified') { - orgField = aql`org.verified` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` - } else if (orderBy.field === 'summary-mail-pass') { - orgField = aql`org.summaries.mail.pass` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` - } else if (orderBy.field === 'summary-mail-fail') { - orgField = aql`org.summaries.mail.fail` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` - } else if (orderBy.field === 'summary-mail-total') { - orgField = aql`org.summaries.mail.total` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` - } else if (orderBy.field === 'summary-web-pass') { - orgField = aql`org.summaries.web.pass` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` - } else if (orderBy.field === 'summary-web-fail') { - orgField = aql`org.summaries.web.fail` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` - } else if (orderBy.field === 'summary-web-total') { - orgField = aql`org.summaries.web.total` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` - } else if (orderBy.field === 'domain-count') { - orgField = aql`COUNT(orgDomains)` - hasNextPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND LAST(retrievedOrgs)._id claims RETURN e._to)` - hasPreviousPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND FIRST(retrievedOrgs)._id claims RETURN e._to)` + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` } - hasNextPageFilter = aql` - FILTER ${orgField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${orgField} == ${hasNextPageDocumentField} - AND TO_NUMBER(org._key) > TO_NUMBER(FIRST(retrievedOrgs)._key)) - ` - hasPreviousPageFilter = aql` - FILTER ${orgField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${orgField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'acronym') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).acronym ${orderBy.direction},` - } else if (orderBy.field === 'name') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).name ${orderBy.direction},` - } else if (orderBy.field === 'slug') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).slug ${orderBy.direction},` - } else if (orderBy.field === 'zone') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).zone ${orderBy.direction},` - } else if (orderBy.field === 'sector') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).sector ${orderBy.direction},` - } else if (orderBy.field === 'country') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).country ${orderBy.direction},` - } else if (orderBy.field === 'province') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).province ${orderBy.direction},` - } else if (orderBy.field === 'city') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).city ${orderBy.direction},` - } else if (orderBy.field === 'verified') { - sortByField = aql`org.verified ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-pass') { - sortByField = aql`org.summaries.mail.pass ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-fail') { - sortByField = aql`org.summaries.mail.fail ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-total') { - sortByField = aql`org.summaries.mail.total ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-pass') { - sortByField = aql`org.summaries.web.pass ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-fail') { - sortByField = aql`org.summaries.web.fail ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-total') { - sortByField = aql`org.summaries.web.total ${orderBy.direction},` - } else if (orderBy.field === 'domain-count') { - sortByField = aql`COUNT(orgDomains) ${orderBy.direction},` + let includeSuperAdminOrgQuery = aql`` + if (!includeSuperAdminOrg) { + includeSuperAdminOrgQuery = aql`FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin"` } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let includeSuperAdminOrgQuery = aql`` - if (!includeSuperAdminOrg) { - includeSuperAdminOrgQuery = aql`FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin"` - } - let orgKeysQuery - if (!loginRequiredBool) { - orgKeysQuery = aql` + let orgKeysQuery + if (!loginRequiredBool) { + orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users LET keys = ( FOR org IN organizations - ${ - includeSuperAdminOrg && isSuperAdmin - ? aql`` - : includeSuperAdminOrgQuery - } + ${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` + } else if (isSuperAdmin) { + orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users LET keys = ( FOR org IN organizations @@ -400,22 +368,21 @@ export const loadOrgConnectionsByDomainId = ({ LET claimKeys = (FOR v, e IN 1..1 INBOUND ${domainId} claims RETURN v._key) LET orgKeys = INTERSECTION(keys, claimKeys) ` - } else if (isAdmin) { - orgKeysQuery = aql` + } else if (isAdmin) { + orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users 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 ) LET claimKeys = (FOR v, e IN 1..1 INBOUND ${domainId} claims RETURN v._key) LET orgKeys = INTERSECTION(keys, claimKeys) ` - } else { - orgKeysQuery = aql` + } else { + orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users LET keys = ( FOR org, e IN 1..1 @@ -426,47 +393,55 @@ export const loadOrgConnectionsByDomainId = ({ LET claimKeys = (FOR v, e IN 1..1 INBOUND ${domainId} claims RETURN v._key) LET orgKeys = INTERSECTION(keys, claimKeys) ` - } + } - let orgQuery = aql`` - let filterString = aql`FILTER org._key IN orgKeys` - let totalCount = aql`LENGTH(orgKeys)` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - orgQuery = aql` + let orgQuery = aql`` + let filterString = aql`FILTER org._key IN orgKeys` + let totalCount = aql`LENGTH(orgKeys)` + if (typeof search !== 'undefined' && search !== '') { + 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` - totalCount = aql`LENGTH(searchedOrgs)` - } + filterString = aql`FILTER org._key IN searchedOrgs` + totalCount = aql`LENGTH(searchedOrgs)` + } - let organizationInfoCursor - try { - organizationInfoCursor = await query` + let organizationInfoCursor + try { + organizationInfoCursor = await query` ${orgKeysQuery} ${orgQuery} @@ -491,8 +466,9 @@ export const loadOrgConnectionsByDomainId = ({ _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(orgDomains), - summaries: org.summaries + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE(${language}, org.orgDetails) ) @@ -525,55 +501,51 @@ export const loadOrgConnectionsByDomainId = ({ "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.`), - ) - } + } 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.`)) + } - let organizationInfo - try { - organizationInfo = await organizationInfoCursor.next() - } catch (err) { - 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.`), - ) - } + let organizationInfo + try { + organizationInfo = await organizationInfoCursor.next() + } catch (err) { + 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.`)) + } - if (organizationInfo.organizations.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, + if (organizationInfo.organizations.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } } - } - const edges = organizationInfo.organizations.map((organization) => { + const edges = organizationInfo.organizations.map((organization) => { + return { + cursor: toGlobalId('organization', organization._key), + node: organization, + } + }) + return { - cursor: toGlobalId('organization', organization._key), - node: organization, + edges, + totalCount: organizationInfo.totalCount, + pageInfo: { + hasNextPage: organizationInfo.hasNextPage, + hasPreviousPage: organizationInfo.hasPreviousPage, + startCursor: toGlobalId('organization', organizationInfo.startKey), + endCursor: toGlobalId('organization', organizationInfo.endKey), + }, } - }) - - return { - edges, - totalCount: organizationInfo.totalCount, - pageInfo: { - hasNextPage: organizationInfo.hasNextPage, - hasPreviousPage: organizationInfo.hasPreviousPage, - startCursor: toGlobalId('organization', organizationInfo.startKey), - endCursor: toGlobalId('organization', organizationInfo.endKey), - }, } -} diff --git a/api/src/organization/loaders/load-organization-connections-by-user-id.js b/api/src/organization/loaders/load-organization-connections-by-user-id.js index 35b9cc0527..ca09acda34 100644 --- a/api/src/organization/loaders/load-organization-connections-by-user-id.js +++ b/api/src/organization/loaders/load-organization-connections-by-user-id.js @@ -2,429 +2,443 @@ import { aql } from 'arangojs' import { fromGlobalId, toGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' -export const loadOrgConnectionsByUserId = ({ - query, - userKey, - cleanseInput, - language, - i18n, - auth: { loginRequiredBool }, -}) => async ({ - after, - before, - first, - last, - orderBy, - isSuperAdmin, - search, - isAdmin, - includeSuperAdminOrg, -}) => { - const userDBId = `users/${userKey}` +export const loadOrgConnectionsByUserId = + ({ query, userKey, cleanseInput, language, i18n, auth: { loginRequiredBool } }) => + async ({ + after, + before, + first, + last, + orderBy, + isSuperAdmin, + search, + isAdmin, + includeSuperAdminOrg, + isVerified, + isAffiliated, + }) => { + 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(org._key) > TO_NUMBER(${afterId})` + 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(org._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`<` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } + + afterVar = aql`LET afterVar = DOCUMENT(organizations, ${afterId})` + + let documentField = aql`` + let orgField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'acronym') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).acronym` + orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + } else if (orderBy.field === 'name') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).name` + orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + } else if (orderBy.field === 'slug') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).slug` + orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + } else if (orderBy.field === 'zone') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).zone` + orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + } else if (orderBy.field === 'sector') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).sector` + orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + } else if (orderBy.field === 'country') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).country` + orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + } else if (orderBy.field === 'province') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).province` + orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + } else if (orderBy.field === 'city') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).city` + orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + } else if (orderBy.field === 'verified') { + documentField = aql`afterVar.verified` + orgField = aql`org.verified` + } else if (orderBy.field === 'summary-mail-pass') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` + } else if (orderBy.field === 'summary-mail-fail') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` + } else if (orderBy.field === 'summary-mail-total') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` + } else if (orderBy.field === 'summary-web-pass') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` + } else if (orderBy.field === 'summary-web-fail') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` + } else if (orderBy.field === 'summary-web-total') { + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` + } else if (orderBy.field === 'domain-count') { + documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` + orgField = aql`COUNT(orgDomains)` + } + + afterTemplate = aql` + FILTER ${orgField} ${afterTemplateDirection} ${documentField} + OR (${orgField} == ${documentField} + AND TO_NUMBER(org._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(org._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`>` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(organizations, ${beforeId})` + + let documentField = aql`` + let orgField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'acronym') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).acronym` + orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + } else if (orderBy.field === 'name') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).name` + orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + } else if (orderBy.field === 'slug') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).slug` + orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + } else if (orderBy.field === 'zone') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).zone` + orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + } else if (orderBy.field === 'sector') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).sector` + orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + } else if (orderBy.field === 'country') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).country` + orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + } else if (orderBy.field === 'province') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).province` + orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + } else if (orderBy.field === 'city') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).city` + orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + } else if (orderBy.field === 'verified') { + documentField = aql`beforeVar.verified` + orgField = aql`org.verified` + } else if (orderBy.field === 'summary-mail-pass') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` + } else if (orderBy.field === 'summary-mail-fail') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` + } else if (orderBy.field === 'summary-mail-total') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` + } else if (orderBy.field === 'summary-web-pass') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` + } else if (orderBy.field === 'summary-web-fail') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` + } else if (orderBy.field === 'summary-web-total') { + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` + } else if (orderBy.field === 'domain-count') { + documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` + orgField = aql`COUNT(orgDomains)` + } + + beforeTemplate = aql` + FILTER ${orgField} ${beforeTemplateDirection} ${documentField} + OR (${orgField} == ${documentField} + AND TO_NUMBER(org._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: loadOrgConnectionsByUserId.`, + ) + throw new Error( + 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.`), + ) + } 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.`)) + } 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.`) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(org._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(org._key) DESC LIMIT TO_NUMBER(${last})` + } } else { - let afterTemplateDirection = aql`<` + 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: loadOrgConnectionsByUserId.`, + ) + 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)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`<` + let hasPreviousPageDirection = aql`>` if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` } - afterVar = aql`LET afterVar = DOCUMENT(organizations, ${afterId})` - - let documentField = aql`` let orgField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` /* istanbul ignore else */ if (orderBy.field === 'acronym') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).acronym` orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).acronym` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).acronym` } else if (orderBy.field === 'name') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).name` orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).name` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).name` } else if (orderBy.field === 'slug') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).slug` orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).slug` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).slug` } else if (orderBy.field === 'zone') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).zone` orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).zone` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).zone` } else if (orderBy.field === 'sector') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).sector` orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).sector` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).sector` } else if (orderBy.field === 'country') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).country` orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).country` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).country` } else if (orderBy.field === 'province') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).province` orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).province` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).province` } else if (orderBy.field === 'city') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).city` orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).city` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).city` } else if (orderBy.field === 'verified') { - documentField = aql`afterVar.verified` orgField = aql`org.verified` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`afterVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`afterVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`afterVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`afterVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`afterVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`afterVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` } else if (orderBy.field === 'domain-count') { - documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` orgField = aql`COUNT(orgDomains)` + hasNextPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND LAST(retrievedOrgs)._id claims RETURN e._to)` + hasPreviousPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND FIRST(retrievedOrgs)._id claims RETURN e._to)` } - afterTemplate = aql` - FILTER ${orgField} ${afterTemplateDirection} ${documentField} - OR (${orgField} == ${documentField} - AND TO_NUMBER(org._key) > TO_NUMBER(${afterId})) - ` + hasNextPageFilter = aql` + FILTER ${orgField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${orgField} == ${hasNextPageDocumentField} + AND TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${orgField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${orgField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)) + ` } - } - 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(org._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`>` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(organizations, ${beforeId})` - - let documentField = aql`` - let orgField = aql`` + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { /* istanbul ignore else */ if (orderBy.field === 'acronym') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).acronym` - orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).acronym ${orderBy.direction},` } else if (orderBy.field === 'name') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).name` - orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).name ${orderBy.direction},` } else if (orderBy.field === 'slug') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).slug` - orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).slug ${orderBy.direction},` } else if (orderBy.field === 'zone') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).zone` - orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).zone ${orderBy.direction},` } else if (orderBy.field === 'sector') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).sector` - orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).sector ${orderBy.direction},` } else if (orderBy.field === 'country') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).country` - orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).country ${orderBy.direction},` } else if (orderBy.field === 'province') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).province` - orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).province ${orderBy.direction},` } else if (orderBy.field === 'city') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).city` - orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).city ${orderBy.direction},` } else if (orderBy.field === 'verified') { - documentField = aql`beforeVar.verified` - orgField = aql`org.verified` + sortByField = aql`org.verified ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`beforeVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`beforeVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`beforeVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.total ${orderBy.direction},` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`beforeVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`beforeVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`beforeVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.total ${orderBy.direction},` } else if (orderBy.field === 'domain-count') { - documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` - orgField = aql`COUNT(orgDomains)` + sortByField = aql`COUNT(orgDomains) ${orderBy.direction},` } - - beforeTemplate = aql` - FILTER ${orgField} ${beforeTemplateDirection} ${documentField} - OR (${orgField} == ${documentField} - AND TO_NUMBER(org._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: loadOrgConnectionsByUserId.`, - ) - throw new Error( - 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.`, - ), - ) - } 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.`, - ), - ) - } 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.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(org._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(org._key) DESC LIMIT TO_NUMBER(${last})` + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` } - } 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: loadOrgConnectionsByUserId.`, - ) - 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)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`<` - let hasPreviousPageDirection = aql`>` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` + let includeSuperAdminOrgQuery = aql`` + if (!includeSuperAdminOrg) { + includeSuperAdminOrgQuery = aql`FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin"` } - let orgField = aql`` - let hasNextPageDocumentField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'acronym') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).acronym` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).acronym` - } else if (orderBy.field === 'name') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).name` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).name` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).name` - } else if (orderBy.field === 'slug') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).slug` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).slug` - } else if (orderBy.field === 'zone') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).zone` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).zone` - } else if (orderBy.field === 'sector') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).sector` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).sector` - } else if (orderBy.field === 'country') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).country` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).country` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).country` - } else if (orderBy.field === 'province') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).province` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).province` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).province` - } else if (orderBy.field === 'city') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).city` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).city` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).city` - } else if (orderBy.field === 'verified') { - orgField = aql`org.verified` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` - } else if (orderBy.field === 'summary-mail-pass') { - orgField = aql`org.summaries.mail.pass` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` - } else if (orderBy.field === 'summary-mail-fail') { - orgField = aql`org.summaries.mail.fail` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` - } else if (orderBy.field === 'summary-mail-total') { - orgField = aql`org.summaries.mail.total` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` - } else if (orderBy.field === 'summary-web-pass') { - orgField = aql`org.summaries.web.pass` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` - } else if (orderBy.field === 'summary-web-fail') { - orgField = aql`org.summaries.web.fail` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` - } else if (orderBy.field === 'summary-web-total') { - orgField = aql`org.summaries.web.total` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` - } else if (orderBy.field === 'domain-count') { - orgField = aql`COUNT(orgDomains)` - hasNextPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND LAST(retrievedOrgs)._id claims RETURN e._to)` - hasPreviousPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND FIRST(retrievedOrgs)._id claims RETURN e._to)` + let isVerifiedQuery = aql`` + if (isVerified) { + isVerifiedQuery = aql`FILTER org.verified == true` } - hasNextPageFilter = aql` - FILTER ${orgField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${orgField} == ${hasNextPageDocumentField} - AND TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)) - ` - hasPreviousPageFilter = aql` - FILTER ${orgField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${orgField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'acronym') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).acronym ${orderBy.direction},` - } else if (orderBy.field === 'name') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).name ${orderBy.direction},` - } else if (orderBy.field === 'slug') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).slug ${orderBy.direction},` - } else if (orderBy.field === 'zone') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).zone ${orderBy.direction},` - } else if (orderBy.field === 'sector') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).sector ${orderBy.direction},` - } else if (orderBy.field === 'country') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).country ${orderBy.direction},` - } else if (orderBy.field === 'province') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).province ${orderBy.direction},` - } else if (orderBy.field === 'city') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).city ${orderBy.direction},` - } else if (orderBy.field === 'verified') { - sortByField = aql`org.verified ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-pass') { - sortByField = aql`org.summaries.mail.pass ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-fail') { - sortByField = aql`org.summaries.mail.fail ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-total') { - sortByField = aql`org.summaries.mail.total ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-pass') { - sortByField = aql`org.summaries.web.pass ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-fail') { - sortByField = aql`org.summaries.web.fail ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-total') { - sortByField = aql`org.summaries.web.total ${orderBy.direction},` - } else if (orderBy.field === 'domain-count') { - sortByField = aql`COUNT(orgDomains) ${orderBy.direction},` + let isAdminFilter = aql`` + if (isAdmin) { + isAdminFilter = aql`FILTER e.permission IN ["admin", "owner", "super_admin"]` } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let includeSuperAdminOrgQuery = aql`` - if (!includeSuperAdminOrg) { - includeSuperAdminOrgQuery = aql`FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin"` - } - let orgKeysQuery - if (isSuperAdmin) { - orgKeysQuery = aql` + 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) { - orgKeysQuery = aql` + } 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) { + } else if (!loginRequiredBool) { orgKeysQuery = aql` - WITH claims, domains, organizations, organizationSearch + 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 - FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin" + ${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 ) ` } - } - let orgQuery = aql`` - let filterString = aql`FILTER org._key IN orgKeys` - let totalCount = aql`LENGTH(orgKeys)` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - orgQuery = aql` + let orgQuery = aql`` + let filterString = aql`FILTER org._key IN orgKeys` + let totalCount = aql`LENGTH(orgKeys)` + if (typeof search !== 'undefined' && search !== '') { + 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( @@ -432,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( @@ -443,17 +462,22 @@ 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` - totalCount = aql`LENGTH(searchedOrgs)` - } + filterString = aql`FILTER org._key IN searchedOrgs` + totalCount = aql`LENGTH(searchedOrgs)` + } - let organizationInfoCursor - try { - organizationInfoCursor = await query` + let organizationInfoCursor + try { + organizationInfoCursor = await query` ${orgKeysQuery} ${orgQuery} @@ -478,8 +502,9 @@ export const loadOrgConnectionsByUserId = ({ _type: "organization", id: org._key, verified: org.verified, + externalId: org.externalId, domainCount: COUNT(orgDomains), - summaries: org.summaries + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE(${language}, org.orgDetails) ) @@ -512,55 +537,51 @@ export const loadOrgConnectionsByUserId = ({ "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.`), - ) - } + } 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.`)) + } - let organizationInfo - try { - organizationInfo = await organizationInfoCursor.next() - } catch (err) { - 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.`), - ) - } + let organizationInfo + try { + organizationInfo = await organizationInfoCursor.next() + } catch (err) { + 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.`)) + } - if (organizationInfo.organizations.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, + if (organizationInfo.organizations.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } } - } - const edges = organizationInfo.organizations.map((org) => { + const edges = organizationInfo.organizations.map((org) => { + return { + cursor: toGlobalId('organization', org._key), + node: org, + } + }) + return { - cursor: toGlobalId('organization', org._key), - node: org, + edges, + totalCount: organizationInfo.totalCount, + pageInfo: { + hasNextPage: organizationInfo.hasNextPage, + hasPreviousPage: organizationInfo.hasPreviousPage, + startCursor: toGlobalId('organization', organizationInfo.startKey), + endCursor: toGlobalId('organization', organizationInfo.endKey), + }, } - }) - - return { - edges, - totalCount: organizationInfo.totalCount, - pageInfo: { - hasNextPage: organizationInfo.hasNextPage, - hasPreviousPage: organizationInfo.hasPreviousPage, - startCursor: toGlobalId('organization', organizationInfo.startKey), - endCursor: toGlobalId('organization', organizationInfo.endKey), - }, } -} 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..d30cab85ba --- /dev/null +++ b/api/src/organization/loaders/load-organization-summaries-by-period.js @@ -0,0 +1,64 @@ +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 + } + + let startDateFilter = aql`` + if (cleansedStartDate !== null) { + 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 organizationSummaries + FILTER summary.organization == ${orgId} + ${startDateFilter} + ${endDateFilter} + SORT summary.date ${sortDirection} + ${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 index 7e3a6cd1cf..3ab87077a2 100644 --- a/api/src/organization/mutations/__tests__/create-organization.test.js +++ b/api/src/organization/mutations/__tests__/create-organization.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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -8,48 +9,16 @@ 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 { checkSuperAdmin, superAdminRequired, userRequired, verifiedRequired } from '../../../auth' import { loadUserByKey } from '../../../user/loaders' -import { loadOrgBySlug } from '../../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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('create an organization', () => { - let query, drop, truncate, schema, collections, transaction, user + let query, drop, truncate, schema, collections, transaction, user, i18n const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -64,6 +33,18 @@ describe('create an organization', () => { 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 @@ -95,9 +76,9 @@ describe('create an organization', () => { }) describe('language is set to english', () => { it('returns the organizations information', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { createOrganization( input: { @@ -105,16 +86,8 @@ describe('create an organization', () => { 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" + externalId: "EXT123" + verified: false } ) { result { @@ -123,21 +96,18 @@ describe('create an organization', () => { acronym slug name - zone - sector - country - province - city verified + externalId } } } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'en', + ip: '1.2.3.4', }, query, collections: collectionNames, @@ -149,9 +119,22 @@ describe('create an organization', () => { 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: { - loadOrgBySlug: loadOrgBySlug({ query, language: 'en' }), loadUserByKey: loadUserByKey({ query }), }, validators: { @@ -159,7 +142,7 @@ describe('create an organization', () => { slugify, }, }, - ) + }) const orgCursor = await query` FOR org IN organizations @@ -177,28 +160,24 @@ describe('create an organization', () => { 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, + 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}`, - ]) + 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( + const response = await graphql({ schema, - ` + source: ` mutation { createOrganization( input: { @@ -206,16 +185,8 @@ describe('create an organization', () => { 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" + externalId: "EXT123" + verified: false } ) { result { @@ -224,21 +195,18 @@ describe('create an organization', () => { acronym slug name - zone - sector - country - province - city verified + externalId } } } } `, - null, - { + rootValue: null, + contextValue: { request: { language: 'fr', + ip: '1.2.3.4', }, query, collections: collectionNames, @@ -250,9 +218,22 @@ describe('create an organization', () => { 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: { - loadOrgBySlug: loadOrgBySlug({ query, language: 'fr' }), loadUserByKey: loadUserByKey({ query }), }, validators: { @@ -260,7 +241,7 @@ describe('create an organization', () => { slugify, }, }, - ) + }) const orgCursor = await query` FOR org IN organizations @@ -278,17 +259,15 @@ describe('create an organization', () => { 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, + 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`, @@ -297,27 +276,12 @@ describe('create an organization', () => { }) }) 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( + const response = await graphql({ schema, - ` + source: ` mutation { createOrganization( input: { @@ -325,16 +289,6 @@ describe('create an organization', () => { 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 { @@ -343,11 +297,6 @@ describe('create an organization', () => { acronym slug name - zone - sector - country - province - city verified } ... on OrganizationError { @@ -358,8 +307,8 @@ describe('create an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request: { language: 'en', @@ -373,11 +322,11 @@ describe('create an organization', () => { _key: 123, }), verifiedRequired: jest.fn(), + checkSuperAdmin: jest.fn(), + superAdminRequired: jest.fn(), }, + dataSources: { organization: { bySlug: { loadMany: jest.fn().mockReturnValue([{}, undefined]) } } }, loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([{}, undefined]), - }, loadUserByKey: jest.fn(), }, validators: { @@ -385,15 +334,14 @@ describe('create an organization', () => { slugify, }, }, - ) + }) const error = { data: { createOrganization: { result: { code: 400, - description: - 'Organization name already in use. Please try again with a different name.', + description: 'Organization name already in use. Please try again with a different name.', }, }, }, @@ -405,278 +353,61 @@ describe('create an organization', () => { ]) }) }) - 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 - } - } + 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" } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections: collectionNames, - 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 + verified } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } + ... on OrganizationError { + code + description } } } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections: collectionNames, - 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, - }, + } + `, + 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(), }, - ) - - 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: collectionNames, - 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, + 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.', - ), - ] + 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`, - ]) - }) + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) }) }) }) @@ -697,9 +428,9 @@ describe('create an organization', () => { }) describe('organization already exists', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { createOrganization( input: { @@ -707,16 +438,6 @@ describe('create an organization', () => { 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 { @@ -725,11 +446,6 @@ describe('create an organization', () => { acronym slug name - zone - sector - country - province - city verified } ... on OrganizationError { @@ -740,8 +456,8 @@ describe('create an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request: { language: 'en', @@ -755,11 +471,11 @@ describe('create an organization', () => { _key: 123, }), verifiedRequired: jest.fn(), + checkSuperAdmin: jest.fn(), + superAdminRequired: jest.fn(), }, + dataSources: { organization: { bySlug: { loadMany: jest.fn().mockReturnValue([{}, undefined]) } } }, loaders: { - loadOrgBySlug: { - loadMany: jest.fn().mockReturnValue([{}, undefined]), - }, loadUserByKey: jest.fn(), }, validators: { @@ -767,15 +483,14 @@ describe('create an organization', () => { 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.", + description: "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.", }, }, }, @@ -787,278 +502,61 @@ describe('create an organization', () => { ]) }) }) - 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 - } - } + 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" } - } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections: collectionNames, - 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 + verified } - ) { - result { - ... on Organization { - id - acronym - slug - name - zone - sector - country - province - city - verified - } - ... on OrganizationError { - code - description - } + ... on OrganizationError { + code + description } } } - `, - null, - { - i18n, - request: { - language: 'en', - }, - query, - collections: collectionNames, - 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, - }, + } + `, + 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(), }, - ) - - 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: collectionNames, - 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, + 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.', - ), - ] + 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`, - ]) - }) + 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 index 7d4c0caddc..7b179c6d81 100644 --- a/api/src/organization/mutations/__tests__/remove-organization.test.js +++ b/api/src/organization/mutations/__tests__/remove-organization.test.js @@ -1,5 +1,6 @@ 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' @@ -10,44 +11,12 @@ 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' +import { OrganizationDataSource } from '../../data-source' import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('removing an organization', () => { let query, drop, truncate, schema, collections, transaction, user, i18n @@ -104,38 +73,27 @@ describe('removing an organization', () => { 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({ + + 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, }) @@ -207,9 +165,9 @@ describe('removing an organization', () => { }) }) it('removes the dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -231,13 +189,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -250,12 +209,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -267,14 +237,13 @@ describe('removing an organization', () => { 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) }) it('removes the ownership edge', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -296,13 +265,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -315,26 +285,36 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -356,8 +336,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -375,12 +355,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -392,16 +383,15 @@ describe('removing an organization', () => { 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() }) }) describe('org is the only one claiming the domain', () => { - it('removes the dkim result data', async () => { - await graphql( + it('removes the web scan result data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -423,13 +413,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -442,24 +433,34 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` + await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` - const testDkimResultCursor = - await query`FOR dkimResult IN dkimResults OPTIONS { waitForSync: true } RETURN dkimResult` - const testDkimResult = await testDkimResultCursor.next() - expect(testDkimResult).toEqual(undefined) + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -481,13 +482,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -500,48 +502,40 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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) + await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toEqual(undefined) + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).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) }) + it('removes the domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -563,13 +557,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -582,24 +577,34 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -621,13 +626,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -640,12 +646,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -669,10 +686,10 @@ describe('removing an organization', () => { _to: domain._id, }) }) - it('does not remove the dkim result', async () => { - await graphql( + it('does not remove the web scan result data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -694,8 +711,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -713,24 +730,33 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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() + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -752,8 +778,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -771,48 +797,36 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -834,8 +848,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -853,24 +867,34 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -892,13 +916,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -911,12 +936,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -932,178 +968,6 @@ describe('removing an organization', () => { 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: collectionNames, - 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: collectionNames, - 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 () => { @@ -1163,9 +1027,9 @@ describe('removing an organization', () => { }) }) it('removes the dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1187,13 +1051,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1206,12 +1071,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -1223,14 +1099,13 @@ describe('removing an organization', () => { 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) }) it('removes the ownership edge', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1252,13 +1127,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1271,26 +1147,36 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1312,8 +1198,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1331,12 +1217,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -1348,16 +1245,15 @@ describe('removing an organization', () => { 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() }) }) describe('org is the only one claiming the domain', () => { - it('removes the dkim result data', async () => { - await graphql( + it('removes the web scan result data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1379,13 +1275,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1398,24 +1295,32 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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) + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1437,13 +1342,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1456,48 +1362,36 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ query, language: 'en' }), - loadUserByKey: loadUserByKey({ query }), + 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 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 testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` + const testDNS = await testDNSCursor.next() + expect(testDNS).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) }) it('removes the domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1519,13 +1413,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1538,24 +1433,34 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1577,13 +1482,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1596,12 +1502,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -1625,10 +1542,10 @@ describe('removing an organization', () => { _to: domain._id, }) }) - it('does not remove the dkim result data', async () => { - await graphql( + it('does not remove the web scan result data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1650,8 +1567,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1669,24 +1586,33 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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() + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1708,8 +1634,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1727,48 +1653,36 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1790,8 +1704,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1809,24 +1723,34 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -1848,13 +1772,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -1867,12 +1792,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -1888,181 +1824,9 @@ describe('removing an organization', () => { 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: collectionNames, - 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: collectionNames, - 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', () => { + describe('users permission is owner', () => { beforeEach(async () => { org = await collections.organizations.save({ verified: false, @@ -2092,7 +1856,7 @@ describe('removing an organization', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', + permission: 'owner', }) await collections.claims.save({ _from: org._id, @@ -2108,9 +1872,9 @@ describe('removing an organization', () => { }) }) it('removes the dmarc summary data', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2132,13 +1896,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2151,12 +1916,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -2168,14 +1944,13 @@ describe('removing an organization', () => { 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) }) it('removes the ownership edge', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2197,13 +1972,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2216,26 +1992,36 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2257,8 +2043,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2276,12 +2062,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -2293,16 +2090,15 @@ describe('removing an organization', () => { 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() }) }) describe('org is the only one claiming the domain', () => { - it('removes the dkim result data', async () => { - await graphql( + it('removes the web scan result data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2324,13 +2120,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2343,24 +2140,33 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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) + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2382,13 +2188,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2401,48 +2208,36 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).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.web` + const testWeb = await testWebCursor.next() + expect(testWeb).toEqual(undefined) }) it('removes the domain', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2464,13 +2259,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2483,24 +2279,34 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2522,13 +2328,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2541,12 +2348,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -2570,10 +2388,10 @@ describe('removing an organization', () => { _to: domain._id, }) }) - it('does not remove the dkim result data', async () => { - await graphql( + it('does not remove the web scan result data', async () => { + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2595,8 +2413,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2614,24 +2432,33 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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() + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2653,8 +2480,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2672,48 +2499,36 @@ describe('removing an organization', () => { 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() + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ query }), + }, + }, + }) - const testHttpsCursor = - await query`FOR httpsScan IN https OPTIONS { waitForSync: true } RETURN httpsScan` - const testHttps = await testHttpsCursor.next() - expect(testHttps).toBeDefined() + const testDNSCursor = await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` + const testDNS = await testDNSCursor.next() + expect(testDNS).toEqual(true) - const testSslCursor = - await query`FOR sslScan IN ssl OPTIONS { waitForSync: true } RETURN sslScan` - const testSsl = await testSslCursor.next() - expect(testSsl).toBeDefined() + 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2735,8 +2550,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2754,24 +2569,34 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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 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( + await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -2793,13 +2618,14 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { checkPermission: checkPermission({ userKey: user._key, @@ -2812,12 +2638,23 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, + dataSources: { + auditLogs: { logActivity: jest.fn() }, + organization: new OrganizationDataSource({ + query, + userKey: user._key, + i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, + }), + }, 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` @@ -2833,178 +2670,6 @@ describe('removing an organization', () => { 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: collectionNames, - 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: collectionNames, - 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}.`, - ]) - }) - }) }) }) }) @@ -3026,9 +2691,9 @@ describe('removing an organization', () => { }) describe('the requested org is undefined', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -3050,26 +2715,25 @@ describe('removing an organization', () => { } } `, - null, - { + 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 }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue(undefined), - }, - }, + } } }, }, - ) + }) const expectedResponse = { data: { @@ -3090,12 +2754,12 @@ describe('removing an organization', () => { }) describe('given an incorrect permission', () => { describe('users belong to the org', () => { - describe('users role is admin', () => { + describe('users role is owner', () => { describe('user attempts to remove a verified org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -3117,21 +2781,21 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: 123, + request: { ip: '127.0.0.1' }, auth: { - checkPermission: jest.fn().mockReturnValue('admin'), + checkPermission: jest.fn().mockReturnValue('owner'), userRequired: jest.fn(), verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ _key: 123, verified: true, @@ -3158,10 +2822,9 @@ describe('removing an organization', () => { }, }, }), - }, - }, + } } }, }, - ) + }) const expectedResponse = { data: { @@ -3185,9 +2848,9 @@ describe('removing an organization', () => { describe('users role is user', () => { describe('they attempt to remove the org', () => { it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { removeOrganization( input: { @@ -3209,21 +2872,21 @@ describe('removing an organization', () => { } } `, - null, - { + 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 }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ _key: 123, verified: false, @@ -3250,10 +2913,9 @@ describe('removing an organization', () => { }, }, }), - }, - }, + } } }, }, - ) + }) const expectedResponse = { data: { @@ -3276,2407 +2938,71 @@ describe('removing an 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 - } - } - } + describe('given a data source error', () => { + it('throws an error', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + removeOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - 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 - } + ) { + result { + ... on OrganizationResult { + status + organization { + name } } + ... on OrganizationError { + code + description + } } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn(), - verifiedRequired: jest.fn(), - }, - validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + } + } + `, + 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({ - _key: 123, + _id: 'organizations/123', + _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', - }, - }, + 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([ - `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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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.", - ), - ] + 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`, - ]) + 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 index cbd80ebc58..1a169220ba 100644 --- a/api/src/organization/mutations/__tests__/update-organization.test.js +++ b/api/src/organization/mutations/__tests__/update-organization.test.js @@ -1,6 +1,5 @@ import { setupI18n } from '@lingui/core' -import { ensure, dbNameFromFile } from 'arango-tools' -import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { graphql, GraphQLSchema } from 'graphql' import { toGlobalId } from 'graphql-relay' import { createQuerySchema } from '../../../query' @@ -8,4280 +7,866 @@ 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' -import dbschema from '../../../../database.json' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - -describe('updating an organization', () => { - let query, drop, truncate, schema, collections, transaction, user +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 = [] - 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 + 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 }, + }) }) - beforeEach(() => { + + afterEach(() => { consoleOutput.length = 0 }) - describe('given a successful organization update', () => { - let org - beforeEach(async () => { - // Generate DB Items - ;({ 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', - }, - }, + // 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(), }) - }) - 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', - }) + expect(response.errors).toBeUndefined() + expect(response.data.updateOrganization.result).toMatchObject({ + name: RESOLVED_ORG.name, + acronym: RESOLVED_ORG.acronym, + slug: RESOLVED_ORG.slug, }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(consoleOutput).toContain(`User: user123, successfully updated org ${ORG_KEY}.`) + }) - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TEST', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, - } + it('calls update with correctly merged org details', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Updated Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ orgDS }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + orgKey: ORG_KEY, + updatedOrgDetails: expect.objectContaining({ + orgDetails: expect.objectContaining({ + en: expect.objectContaining({ name: 'Updated Name' }), + }), + }), + }), + ) + }) - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'New Zone', - }, - }, - }, - } + it('preserves existing fields when only partial inputs are provided', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Only EN Updated" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ orgDS }), + }) - 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: collectionNames, - 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 { 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) + }) - 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', - }, - }, - }, - } + it('clears byKey cache before reloading the organization', async () => { + const clear = jest.fn() + const orgDS = makeOrgDS() + orgDS.byKey.clear = clear - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Test" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ orgDS }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(clear).toHaveBeenCalledWith(ORG_KEY) + }) + }) - 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', - }, - }, - }, - } + describe('audit logging', () => { + it('logs audit activity when nameEN is changed', async () => { + const logActivity = jest.fn() - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Brand New Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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}.`, - ]) - }) - }) + } + `, + contextValue: makeContext({ logActivity }), }) - 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: collectionNames, - 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(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', + }), + ]), + }), + }), + ) + }) - 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: collectionNames, - 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 }), - }, - }, - ) + it('logs audit activity when nameFR is changed', async () => { + const logActivity = jest.fn() - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - city: 'Ottawa', - country: 'Canada', - name: 'Test', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, + 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(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: collectionNames, - 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 }), - }, - }, - ) + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + target: expect.objectContaining({ + updatedProperties: expect.arrayContaining([ + expect.objectContaining({ name: 'nameFR', newValue: 'Nouveau Nom' }), + ]), + }), + }), + ) + }) - 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', - }, - }, - }, - } + it('logs audit activity when acronymEN is changed', async () => { + const logActivity = jest.fn() - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" acronymEN: "NEW" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ logActivity }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + target: expect.objectContaining({ + updatedProperties: expect.arrayContaining([ + expect.objectContaining({ name: 'acronymEN', newValue: 'NEW' }), + ]), + }), + }), + ) + }) - 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', - }, - }, - }, - } + it('logs audit activity when acronymFR is changed', async () => { + const logActivity = jest.fn() - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" acronymFR: "NVL" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ logActivity }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + target: expect.objectContaining({ + updatedProperties: expect.arrayContaining([ + expect.objectContaining({ name: 'acronymFR', newValue: 'NVL' }), + ]), + }), + }), + ) + }) - 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', - }, - }, - }, - } + it('does not log when only zone/sector/location fields are updated', async () => { + const logActivity = jest.fn() - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" zoneEN: "NEWZONE" sectorEN: "NEWSECTOR" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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}.`, - ]) - }) - }) + } + `, + contextValue: makeContext({ logActivity }), }) + + expect(logActivity).not.toHaveBeenCalled() }) - 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: collectionNames, - 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', - }, - }, - }, - } + it('populates initiatedBy with user key, userName, role, and IP address', async () => { + const logActivity = jest.fn() - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Some Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ logActivity, ip: '10.0.0.1', permission: 'admin' }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(logActivity).toHaveBeenCalledWith( + expect.objectContaining({ + initiatedBy: { + id: BASE_USER._key, + userName: BASE_USER.userName, + role: 'admin', + ipAddress: '10.0.0.1', + }, + }), + ) + }) + }) - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'TBS', - city: 'Ottawa', - country: 'Canada', - name: 'Treasury Board of Canada Secretariat', - province: 'Ontario', - sector: 'TBS', - zone: 'New Zone', - }, - }, - }, - } + describe('super_admin exclusive fields', () => { + it('super_admin can set externallyManaged to true', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externallyManaged: true }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + updatedOrgDetails: expect.objectContaining({ externallyManaged: true }), + }), + ) + }) - 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', - }, - }, - }, - } + it('super_admin can set externallyManaged to false', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externallyManaged: false }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + updatedOrgDetails: expect.objectContaining({ externallyManaged: false }), + }), + ) + }) - 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', - }, - }, - }, - } + it('admin cannot set externallyManaged — field is omitted from update payload', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externallyManaged: true }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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}.`, - ]) - }) - }) + } + `, + contextValue: makeContext({ permission: 'admin', orgDS }), }) - 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: collectionNames, - 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', - }, - }, - }, - } + const { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails).not.toHaveProperty('externallyManaged') + }) - 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: collectionNames, - 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 }), - }, - }, - ) + it('externallyManaged omitted from input is never set even for super_admin', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - const expectedResponse = { - data: { - updateOrganization: { - result: { - acronym: 'SCT', - city: 'Ottawa', - country: 'Canada', - name: 'Test', - province: 'Ontario', - sector: 'TBS', - zone: 'FED', - }, - }, - }, + 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 }), + }) - 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: collectionNames, - 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 { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails).not.toHaveProperty('externallyManaged') + }) - 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', - }, - }, - }, - } + it('super_admin can update externalId', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externalId: "new-ext-id" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), + }) - 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: collectionNames, - 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 }), - }, - }, - ) + expect(update).toHaveBeenCalledWith( + expect.objectContaining({ + updatedOrgDetails: expect.objectContaining({ externalId: 'new-ext-id' }), + }), + ) + }) - 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', - }, - }, - }, - } + it('admin cannot update externalId — field is omitted from update payload', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" externalId: "should-be-ignored" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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', - }, - }, - }, } + } + `, + contextValue: makeContext({ permission: 'admin', orgDS }), + }) - 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: collectionNames, - 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 { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails).not.toHaveProperty('externalId') + }) - 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', - }, - }, - }, - } + it('super_admin falls back to existing externalId when none is provided', async () => { + const update = jest.fn() + const orgDS = makeOrgDS({ update }) - 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 - } - } - } + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" nameEN: "Some Name" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - `, - null, - { - 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, - 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}.`, - ]) - }) - }) + } + `, + contextValue: makeContext({ permission: 'super_admin', orgDS }), }) + + const { updatedOrgDetails } = update.mock.calls[0][0] + expect(updatedOrgDetails.externalId).toBe(BASE_RAW_ORG.externalId) }) }) - 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: collectionNames, - 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.', - }, - }, - }, + 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).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization: 1, however no organizations is associated with that id.`, - ]) - }) - }) + expect(response.errors).toBeUndefined() + expect(response.data.updateOrganization.result).toEqual({ + code: 400, + description: 'Unable to update unknown organization.', }) - 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: collectionNames, - 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(), - }, - }, - }, - ) + 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 error = { - data: { - updateOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with updating organization.', - }, - }, - }, + 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).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: collectionNames, - 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(), - }, - }, - }, - ) + expect(response.data.updateOrganization.result.code).toBe(400) + expect(response.data.updateOrganization.result.description).not.toBe('Unable to update unknown organization.') + }) + }) - const error = { - data: { - updateOrganization: { - result: { - code: 403, - description: - 'Permission Denied: Please contact organization admin for help with updating 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).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization 123, however they do not have the correct permission level. Permission: undefined`, - ]) - }) - }) - }) + expect(response.errors).toBeUndefined() + expect(response.data.updateOrganization.result).toEqual({ + code: 403, + description: 'Permission Denied: Please contact organization admin for help with updating organization.', }) - 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: collectionNames, - 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(), - }, - }, - }, - ) + expect(consoleOutput).toContain( + `User: user123 attempted to update organization ${ORG_KEY}, however they do not have the correct permission level. Permission: user`, + ) + }) - const error = { - data: { - updateOrganization: { - result: { - code: 400, - description: - 'Organization name already in use, please choose another and try again.', - }, - }, - }, + 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 } + } + } } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to change the name of org: 123 however it is already in use.`, - ]) - }) + `, + contextValue: makeContext({ i18n: frI18n, permission: 'user' }), }) - 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: collectionNames, - 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.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.', + ) + }) - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while retrieving org: 123 for update, err: Error: Database error occurred.`, - ]) - }) - }) + 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 }), }) - 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: collectionNames, - 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: collectionNames, - 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(), - }, - }, - }, - ) + expect(response.data.updateOrganization.result.code).toBe(403) + }) + }) - const error = [ - new GraphQLError( - 'Unable to update organization. Please try again.', - ), - ] + 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 }) - 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.`, - ]) - }) - }) + 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 }), }) - 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: collectionNames, - 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: collectionNames, - 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`, - ]) - }) - }) + 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.`, + ) }) - 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: collectionNames, - 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.', - }, - }, - }, - } + it('returns a translated 400 error when name conflicts (FR)', async () => { + const orgDS = makeOrgDS({ nameInUseCount: 1 }) - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update organization: 1, however no organizations is associated with that id.`, - ]) - }) - }) + 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 }), }) - 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: collectionNames, - 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.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.', + ) + }) - 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: collectionNames, - 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(), - }, - }, - }, - ) + it('skips the name check when neither nameEN nor nameFR is provided', async () => { + const checkNameInUse = jest.fn() + const orgDS = makeOrgDS({ checkNameInUse }) - 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.", - }, - }, - }, + await graphql({ + schema, + source: ` + mutation { + updateOrganization(input: { id: "${ORG_GID}" zoneEN: "NewZone" }) { + result { + ... on Organization { id } + ... on OrganizationError { code description } } - - 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: collectionNames, - 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.`, - ]) - }) + `, + contextValue: makeContext({ orgDS }), }) - 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: collectionNames, - 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(checkNameInUse).not.toHaveBeenCalled() + }) + }) - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while retrieving org: 123 for update, err: Error: Database error occurred.`, - ]) - }) - }) + 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.')), }) - 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: collectionNames, - 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: collectionNames, - 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 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 }), + }) - const error = [ - new GraphQLError( - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - ), - ] + expect(response.errors).toBeDefined() + expect(response.errors[0].message).toBe('Unable to load 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.`, - ]) - }) - }) + it('propagates error thrown by organizationDS.checkNameInUse', async () => { + const orgDS = makeOrgDS({ + checkNameInUse: jest.fn().mockRejectedValue(new Error('Unable to update organization. Please try again.')), }) - 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: collectionNames, - 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.", - ), - ] + 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).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: collectionNames, - 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(), - }, - }, - }, - ) + expect(response.errors).toBeDefined() + expect(response.errors[0].message).toBe('Unable to update organization. Please try again.') + }) - const error = [ - new GraphQLError( - "Impossible de mettre à jour l'organisation. Veuillez réessayer.", - ), - ] + it('propagates error thrown by organizationDS.getRawByKey', async () => { + const orgDS = makeOrgDS({ + getRawByKey: jest.fn().mockRejectedValue(new Error('Unable to load organization. Please try again.')), + }) - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Transaction error occurred while committing org: 123, err: Error: trx commit error`, - ]) - }) - }) + 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 index 4760230822..86a37edfa5 100644 --- a/api/src/organization/mutations/__tests__/verify-organization.test.js +++ b/api/src/organization/mutations/__tests__/verify-organization.test.js @@ -1,5 +1,6 @@ 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' @@ -10,44 +11,13 @@ 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' +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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('removing an organization', () => { let query, drop, truncate, schema, collections, transaction, i18n, user @@ -71,7 +41,7 @@ describe('removing an organization', () => { }) describe('given a successful org verification', () => { - let org + let org, domain beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections, transaction } = await ensure({ @@ -121,6 +91,14 @@ describe('removing an organization', () => { _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() @@ -145,9 +123,9 @@ describe('removing an organization', () => { }) describe('super admin is able to verify organization', () => { it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -169,8 +147,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -194,13 +172,18 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ + dataSources: { + organization: new OrganizationDataSource({ query, - language: 'en', userKey: user._key, i18n, + language: 'en', + cleanseInput, + transaction, + collections: collectionNames, }), + }, + loaders: { loadUserByKey: loadUserByKey({ query, userKey: user._key, @@ -208,14 +191,13 @@ describe('removing an organization', () => { }), }, }, - ) + }) const expectedResponse = { data: { verifyOrganization: { result: { - status: - 'Successfully verified organization: treasury-board-secretariat.', + status: 'Successfully verified organization: treasury-board-secretariat.', organization: { name: 'Treasury Board of Canada Secretariat', }, @@ -225,18 +207,16 @@ describe('removing an organization', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully verified org: ${org._key}.`, - ]) + 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) + 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) }) }) }) @@ -257,9 +237,9 @@ describe('removing an organization', () => { }) describe('super admin is able to verify organization', () => { it('returns a status message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -281,8 +261,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -306,13 +286,18 @@ describe('removing an organization', () => { verifiedRequired: verifiedRequired({}), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: loadOrgByKey({ + dataSources: { + organization: new OrganizationDataSource({ query, - language: 'fr', userKey: user._key, i18n, + language: 'fr', + cleanseInput, + transaction, + collections: collectionNames, }), + }, + loaders: { loadUserByKey: loadUserByKey({ query, userKey: user._key, @@ -320,14 +305,13 @@ describe('removing an organization', () => { }), }, }, - ) + }) const expectedResponse = { data: { verifyOrganization: { result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + 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', }, @@ -337,17 +321,10 @@ describe('removing an organization', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully verified org: ${org._key}.`, - ]) + 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) + 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) }) }) @@ -371,9 +348,9 @@ describe('removing an organization', () => { }) describe('organization is not found', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -395,8 +372,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -408,13 +385,11 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue(undefined), - }, - }, + } } }, }, - ) + }) const error = { data: { @@ -436,9 +411,9 @@ describe('removing an organization', () => { describe('user permission is not super admin', () => { describe('users permission level is admin', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -460,8 +435,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -473,15 +448,13 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ _id: 'organizations/123', }), - }, - }, + } } }, }, - ) + }) const error = { data: { @@ -503,9 +476,9 @@ describe('removing an organization', () => { }) describe('users permission level is user', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -527,8 +500,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -540,15 +513,13 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ _id: 'organizations/123', }), - }, - }, + } } }, }, - ) + }) const error = { data: { @@ -571,9 +542,9 @@ describe('removing an organization', () => { }) describe('organization is already verified', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -595,8 +566,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -608,15 +579,13 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ verified: true, }), - }, - }, + } } }, }, - ) + }) const error = { data: { @@ -635,208 +604,55 @@ describe('removing an organization', () => { ]) }) }) - 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 - } - } - } + describe('data source error occurs', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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 - } + ) { + result { + ... on OrganizationResult { + status + organization { + name } } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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 - } + ... on OrganizationError { + code + description } } } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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, - }), - }, + } + `, + 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) + const error = [new GraphQLError('Unable to verify organization. Please try again.')] - expect(consoleOutput).toEqual([ - `Transaction error occurred while committing newly verified org: 123, err: Error: trx commit error`, - ]) - }) + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([]) }) }) }) @@ -857,9 +673,9 @@ describe('removing an organization', () => { }) describe('organization is not found', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -881,8 +697,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -894,21 +710,18 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue(undefined), - }, - }, + } } }, }, - ) + }) const error = { data: { verifyOrganization: { result: { code: 400, - description: - 'Impossible de vérifier une organisation inconnue.', + description: 'Impossible de vérifier une organisation inconnue.', }, }, }, @@ -923,9 +736,9 @@ describe('removing an organization', () => { describe('user permission is not super admin', () => { describe('users permission level is admin', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -947,8 +760,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -960,15 +773,13 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ _id: 'organizations/123', }), - }, - }, + } } }, }, - ) + }) const error = { data: { @@ -990,9 +801,9 @@ describe('removing an organization', () => { }) describe('users permission level is user', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -1014,8 +825,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1027,15 +838,13 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ _id: 'organizations/123', }), - }, - }, + } } }, }, - ) + }) const error = { data: { @@ -1058,9 +867,9 @@ describe('removing an organization', () => { }) describe('organization is already verified', () => { it('throws an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyOrganization( input: { @@ -1082,8 +891,8 @@ describe('removing an organization', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1095,15 +904,13 @@ describe('removing an organization', () => { verifiedRequired: jest.fn(), }, validators: { cleanseInput }, - loaders: { - loadOrgByKey: { + dataSources: { organization: { byKey: { load: jest.fn().mockReturnValue({ verified: true, }), - }, - }, + } } }, }, - ) + }) const error = { data: { @@ -1122,208 +929,55 @@ describe('removing an organization', () => { ]) }) }) - 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 - } - } - } + describe('data source error occurs', () => { + it('throws an error message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + verifyOrganization( + input: { + orgId: "${toGlobalId('organization', 123)}" } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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 - } + ) { + result { + ... on OrganizationResult { + status + organization { + name } } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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 - } + ... on OrganizationError { + code + description } } } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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, - }), - }, + } + `, + 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) + const error = [new GraphQLError("Impossible de vérifier l'organisation. Veuillez réessayer.")] - expect(consoleOutput).toEqual([ - `Transaction error occurred while committing newly verified org: 123, err: Error: trx commit error`, - ]) - }) + 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 index 435198483b..10c2d638e4 100644 --- a/api/src/organization/mutations/create-organization.js +++ b/api/src/organization/mutations/create-organization.js @@ -1,4 +1,4 @@ -import { GraphQLNonNull, GraphQLString } from 'graphql' +import { GraphQLNonNull, GraphQLString, GraphQLBoolean } from 'graphql' import { mutationWithClientMutationId } from 'graphql-relay' import { t } from '@lingui/macro' @@ -7,81 +7,37 @@ import { createOrganizationUnion } from '../unions' export const createOrganization = new mutationWithClientMutationId({ name: 'CreateOrganization', - description: - 'This mutation allows the creation of an organization inside the database.', + description: 'This mutation allows the creation of an organization inside the database.', inputFields: () => ({ acronymEN: { - type: GraphQLNonNull(Acronym), + type: new GraphQLNonNull(Acronym), description: 'The English acronym of the organization.', }, acronymFR: { - type: GraphQLNonNull(Acronym), + type: new GraphQLNonNull(Acronym), description: 'The French acronym of the organization.', }, nameEN: { - type: GraphQLNonNull(GraphQLString), + type: new GraphQLNonNull(GraphQLString), description: 'The English name of the organization.', }, nameFR: { - type: GraphQLNonNull(GraphQLString), + type: new GraphQLNonNull(GraphQLString), description: 'The French name of the organization.', }, - zoneEN: { - type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the zone the organization belongs to.', + externalId: { + type: GraphQLString, + description: 'String ID used to identify the organization in an external system.', }, - 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.', + verified: { + type: GraphQLBoolean, + description: 'If the organization is verified.', }, }), outputFields: () => ({ result: { type: createOrganizationUnion, - description: - '`CreateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', + description: '`CreateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', resolve: (payload) => payload, }, }), @@ -90,12 +46,10 @@ export const createOrganization = new mutationWithClientMutationId({ { i18n, request, - collections, - transaction, - query, userKey, - auth: { userRequired, verifiedRequired }, - loaders: { loadOrgBySlug }, + request: { ip }, + auth: { userRequired, verifiedRequired, checkSuperAdmin, superAdminRequired }, + dataSources: { auditLogs, organization: organizationDS }, validators: { cleanseInput, slugify }, }, ) => { @@ -103,141 +57,75 @@ export const createOrganization = new mutationWithClientMutationId({ 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 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 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]) + 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}`, - ) + 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.`, - ), + description: i18n._(t`Organization name already in use. Please try again with a different name.`), } } - // Create new organization const organizationDetails = { - verified: false, + verified: args.verified || false, + externallyManaged: false, + externalId, 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, }, }, } - // Setup Trans action - const trx = await transaction(collections) + const organization = await organizationDS.create({ + organizationDetails, + userId: user._id, + language: request.language, + }) - 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}`, - ) + 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 index a2e9b7d45f..2ee2d34e15 100644 --- a/api/src/organization/mutations/index.js +++ b/api/src/organization/mutations/index.js @@ -1,3 +1,4 @@ +export * from './archive-organization' export * from './create-organization' export * from './remove-organization' export * from './update-organization' diff --git a/api/src/organization/mutations/remove-organization.js b/api/src/organization/mutations/remove-organization.js index fd76563982..114566b58b 100644 --- a/api/src/organization/mutations/remove-organization.js +++ b/api/src/organization/mutations/remove-organization.js @@ -3,21 +3,21 @@ 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: GraphQLNonNull(GraphQLID), + type: new 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.', + type: new GraphQLNonNull(removeOrganizationUnion), + description: '`RemoveOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', resolve: (payload) => payload, }, }), @@ -25,13 +25,11 @@ export const removeOrganization = new mutationWithClientMutationId({ args, { i18n, - query, - collections, - transaction, userKey, + request: { ip }, auth: { checkPermission, userRequired, verifiedRequired }, validators: { cleanseInput }, - loaders: { loadOrgByKey }, + dataSources: { auditLogs, organization: organizationDS }, }, ) => { // Get user @@ -43,13 +41,11 @@ export const removeOrganization = new mutationWithClientMutationId({ const { type: _orgType, id: orgId } = fromGlobalId(cleanseInput(args.orgId)) // Get org from db - const organization = await loadOrgByKey.load(orgId) + 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.`, - ) + console.warn(`User: ${userKey} attempted to remove org: ${orgId}, but there is no org associated with that id.`) return { _type: 'error', code: 400, @@ -60,7 +56,7 @@ export const removeOrganization = new mutationWithClientMutationId({ // Get users permission const permission = await checkPermission({ orgId: organization._id }) - if (permission !== 'super_admin' && permission !== 'admin') { + 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.`, ) @@ -74,396 +70,41 @@ export const removeOrganization = new mutationWithClientMutationId({ } // Check to see if org is verified check, and the user is super admin - if (organization.verified && permission !== '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.`, - ), + description: i18n._(t`Permission Denied: Please contact super admin for help with removing organization.`), } } - // Setup Trans action - const trx = await transaction(collections) - - // 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}.`, - ) + 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}.`, - ), + 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 index 36135ce137..8e6938a2e7 100644 --- a/api/src/organization/mutations/update-organization.js +++ b/api/src/organization/mutations/update-organization.js @@ -1,17 +1,17 @@ -import { GraphQLString, GraphQLNonNull, GraphQLID } from 'graphql' +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.', + description: 'Mutation allows the modification of organizations if any changes to the organization may occur.', inputFields: () => ({ id: { - type: GraphQLNonNull(GraphQLID), + type: new GraphQLNonNull(GraphQLID), description: 'The global id of the organization to be updated.', }, acronymEN: { @@ -32,60 +32,57 @@ export const updateOrganization = new mutationWithClientMutationId({ }, zoneEN: { type: GraphQLString, - description: - 'The English translation of the zone the organization belongs to.', + 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.', + 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.', + 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.', + 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.', + 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.', + 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.', + 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.', + 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.', + 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.', + 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: GraphQLNonNull(updateOrganizationUnion), - description: - '`UpdateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', + type: new GraphQLNonNull(updateOrganizationUnion), + description: '`UpdateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', resolve: (payload) => payload, }, }), @@ -93,12 +90,10 @@ export const updateOrganization = new mutationWithClientMutationId({ args, { i18n, - query, - collections, - transaction, userKey, + request: { ip }, auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadOrgByKey }, + dataSources: { auditLogs, organization: organizationDS }, validators: { cleanseInput, slugify }, }, ) => { @@ -123,13 +118,14 @@ export const updateOrganization = new mutationWithClientMutationId({ 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 loadOrgByKey.load(orgKey) + const currentOrg = await organizationDS.byKey.load(orgKey) if (typeof currentOrg === 'undefined') { console.warn( @@ -145,7 +141,7 @@ export const updateOrganization = new mutationWithClientMutationId({ // Check to see if user has permission const permission = await checkPermission({ orgId: currentOrg._id }) - if (permission !== 'admin' && permission !== 'super_admin') { + 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}`, ) @@ -160,23 +156,7 @@ export const updateOrganization = new mutationWithClientMutationId({ // 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.`), - ) - } - + 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.`, @@ -184,42 +164,12 @@ export const updateOrganization = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Organization name already in use, please choose another and try again.`, - ), + 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 compareOrg = await organizationDS.getRawByKey({ orgKey }) const updatedOrgDetails = { orgDetails: { @@ -246,45 +196,70 @@ export const updateOrganization = new mutationWithClientMutationId({ }, } - // Setup Trans action - const trx = await transaction(collections) - - // 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.`), - ) + if (ac.can(permission).updateAny('organization').granted && typeof args.externallyManaged !== 'undefined') { + updatedOrgDetails.externallyManaged = args.externallyManaged } - 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.`), - ) + if (ac.can(permission).updateAny('organization').granted) { + updatedOrgDetails.externalId = externalId || compareOrg?.externalId } - await loadOrgByKey.clear(orgKey) - const organization = await loadOrgByKey.load(orgKey) + 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 index 24d65f0a28..dbb2bb2530 100644 --- a/api/src/organization/mutations/verify-organization.js +++ b/api/src/organization/mutations/verify-organization.js @@ -9,15 +9,14 @@ export const verifyOrganization = new mutationWithClientMutationId({ description: 'Mutation allows the verification of an organization.', inputFields: () => ({ orgId: { - type: GraphQLNonNull(GraphQLID), + 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.', + description: '`VerifyOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', resolve: (payload) => payload, }, }), @@ -25,12 +24,9 @@ export const verifyOrganization = new mutationWithClientMutationId({ args, { i18n, - query, - collections, - transaction, userKey, auth: { checkPermission, userRequired, verifiedRequired }, - loaders: { loadOrgByKey }, + dataSources: { organization: organizationDS }, validators: { cleanseInput }, }, ) => { @@ -42,7 +38,7 @@ export const verifyOrganization = new mutationWithClientMutationId({ const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) // Check to see if org exists - const currentOrg = await loadOrgByKey.load(orgKey) + const currentOrg = await organizationDS.byKey.load(orgKey) if (typeof currentOrg === 'undefined') { console.warn( @@ -83,73 +79,15 @@ export const verifyOrganization = new mutationWithClientMutationId({ } } - // Set org to verified currentOrg.verified = true - // Setup Trans action - const trx = await transaction(collections) - - // 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.`), - ) - } + await organizationDS.verify({ currentOrg }) console.info(`User: ${userKey}, successfully verified org: ${orgKey}.`) return { _type: 'result', - status: i18n._( - t`Successfully verified organization: ${currentOrg.slug}.`, - ), + status: i18n._(t`Successfully verified organization: ${currentOrg.slug}.`), organization: currentOrg, } }, diff --git a/api/src/organization/objects/__tests__/organization-summary.test.js b/api/src/organization/objects/__tests__/organization-summary.test.js index ee4ce5a9b5..ff202a755d 100644 --- a/api/src/organization/objects/__tests__/organization-summary.test.js +++ b/api/src/organization/objects/__tests__/organization-summary.test.js @@ -33,6 +33,30 @@ describe('given the organization summary object', () => { 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', () => { @@ -48,10 +72,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ @@ -83,10 +104,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ @@ -120,10 +138,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ @@ -155,10 +170,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ @@ -191,10 +203,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ @@ -226,10 +235,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ @@ -262,10 +268,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ @@ -296,10 +299,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ @@ -326,7 +326,6 @@ describe('given the organization summary object', () => { const demoType = organizationSummaryType.getFields() const dmarcPhase = { - not_implemented: 0, assess: 0, deploy: 0, enforce: 0, @@ -337,26 +336,14 @@ describe('given the organization summary object', () => { const i18n = { _: jest .fn() - .mockReturnValueOnce('not_implemented') .mockReturnValueOnce('assess') .mockReturnValueOnce('deploy') .mockReturnValueOnce('enforce') .mockReturnValueOnce('maintain'), } - expect( - demoType.dmarcPhase.resolve( - { dmarc_phase: dmarcPhase }, - {}, - { i18n }, - ), - ).toEqual({ + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ categories: [ - { - count: 0, - name: 'not implemented', - percentage: 0, - }, { count: 0, name: 'assess', @@ -388,37 +375,24 @@ describe('given the organization summary object', () => { const demoType = organizationSummaryType.getFields() const dmarcPhase = { - not_implemented: 50, assess: 75, deploy: 100, enforce: 125, - maintain: 150, + maintain: 200, total: 500, } const i18n = { _: jest .fn() - .mockReturnValueOnce('not_implemented') .mockReturnValueOnce('assess') .mockReturnValueOnce('deploy') .mockReturnValueOnce('enforce') .mockReturnValueOnce('maintain'), } - expect( - demoType.dmarcPhase.resolve( - { dmarc_phase: dmarcPhase }, - {}, - { i18n }, - ), - ).toEqual({ + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ categories: [ - { - count: 50, - name: 'not implemented', - percentage: 10.0, - }, { count: 75, name: 'assess', @@ -435,9 +409,9 @@ describe('given the organization summary object', () => { percentage: 25.0, }, { - count: 150, + count: 200, name: 'maintain', - percentage: 30.0, + percentage: 40.0, }, ], total: 500, diff --git a/api/src/organization/objects/__tests__/organization.test.js b/api/src/organization/objects/__tests__/organization.test.js index fa71077a3c..b88914a82f 100644 --- a/api/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/src/organization/objects/organization-summary.js b/api/src/organization/objects/organization-summary.js index 68e00334f6..dbeaa82489 100644 --- a/api/src/organization/objects/organization-summary.js +++ b/api/src/organization/objects/organization-summary.js @@ -1,36 +1,41 @@ 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.', + description: 'Summary based on DMARC scan results for a given organization.', resolve: ({ dmarc }, _) => { - let percentPass, percentageFail - if (dmarc.total <= 0) { - percentPass = 0 - percentageFail = 0 - } else { - percentPass = Number(((dmarc.pass / dmarc.total) * 100).toFixed(1)) - percentageFail = Number(((dmarc.fail / dmarc.total) * 100).toFixed(1)) - } - const categories = [ - { - name: 'pass', - count: dmarc.pass, - percentage: percentPass, - }, - { - name: 'fail', - count: dmarc.fail, - percentage: percentageFail, - }, + createCategory('pass', dmarc.pass, dmarc.total), + createCategory('fail', dmarc.fail, dmarc.total), ] return { @@ -41,29 +46,11 @@ export const organizationSummaryType = new GraphQLObjectType({ }, https: { type: categorizedSummaryType, - description: - 'Summary based on HTTPS scan results for a given organization.', + description: 'Summary based on HTTPS scan results for a given organization.', resolve: ({ https }, _) => { - let percentPass, percentageFail - if (https.total <= 0) { - percentPass = 0 - percentageFail = 0 - } else { - percentPass = Number(((https.pass / https.total) * 100).toFixed(1)) - percentageFail = Number(((https.fail / https.total) * 100).toFixed(1)) - } - const categories = [ - { - name: 'pass', - count: https.pass, - percentage: percentPass, - }, - { - name: 'fail', - count: https.fail, - percentage: percentageFail, - }, + createCategory('pass', https.pass, https.total), + createCategory('fail', https.fail, https.total), ] return { @@ -74,29 +61,11 @@ export const organizationSummaryType = new GraphQLObjectType({ }, mail: { type: categorizedSummaryType, - description: - 'Summary based on mail scan results for a given organization.', + 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, - }, + createCategory('pass', mail.pass, mail.total), + createCategory('fail', mail.fail, mail.total), ] return { @@ -107,30 +76,9 @@ export const organizationSummaryType = new GraphQLObjectType({ }, web: { type: categorizedSummaryType, - description: - 'Summary based on web scan results for a given organization.', + 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, - }, - ] + const categories = [createCategory('pass', web.pass, web.total), createCategory('fail', web.fail, web.total)] return { categories, @@ -141,70 +89,98 @@ export const organizationSummaryType = new GraphQLObjectType({ dmarcPhase: { type: categorizedSummaryType, description: 'Summary based on DMARC phases for a given organization.', - resolve: ({ dmarc_phase }, _) => { - let percentNotImplemented, - percentAsses, - percentDeploy, - percentEnforce, - percentMaintain - if (dmarc_phase.total <= 0) { - percentNotImplemented = 0 - percentAsses = 0 - percentDeploy = 0 - percentEnforce = 0 - percentMaintain = 0 - } else { - percentNotImplemented = Number( - ((dmarc_phase.not_implemented / dmarc_phase.total) * 100).toFixed( - 1, - ), - ) - percentAsses = Number( - ((dmarc_phase.assess / dmarc_phase.total) * 100).toFixed(1), - ) - percentDeploy = Number( - ((dmarc_phase.deploy / dmarc_phase.total) * 100).toFixed(1), - ) - percentEnforce = Number( - ((dmarc_phase.enforce / dmarc_phase.total) * 100).toFixed(1), - ) - percentMaintain = Number( - ((dmarc_phase.maintain / dmarc_phase.total) * 100).toFixed(1), - ) + 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 = [ - { - name: 'not implemented', - count: dmarc_phase.not_implemented, - percentage: percentNotImplemented, - }, - { - name: 'assess', - count: dmarc_phase.assess, - percentage: percentAsses, - }, - { - name: 'deploy', - count: dmarc_phase.deploy, - percentage: percentDeploy, - }, - { - name: 'enforce', - count: dmarc_phase.enforce, - percentage: percentEnforce, - }, - { - name: 'maintain', - count: dmarc_phase.maintain, - percentage: percentMaintain, - }, + createCategory('pass', dkim.pass, dkim.total), + createCategory('fail', dkim.fail, dkim.total), ] return { categories, - total: dmarc_phase.total, + 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 index 7601a6284a..9dc814967b 100644 --- a/api/src/organization/objects/organization.js +++ b/api/src/organization/objects/organization.js @@ -1,10 +1,5 @@ import { t } from '@lingui/macro' -import { - GraphQLBoolean, - GraphQLInt, - GraphQLObjectType, - GraphQLString, -} from 'graphql' +import { GraphQLBoolean, GraphQLInt, GraphQLObjectType, GraphQLString, GraphQLList, GraphQLNonNull } from 'graphql' import { connectionArgs, globalIdField } from 'graphql-relay' import { organizationSummaryType } from './organization-summary' @@ -12,8 +7,11 @@ 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 { 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', @@ -64,17 +62,233 @@ export const organizationType = new GraphQLObjectType({ 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.', + 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.', @@ -85,22 +299,28 @@ export const organizationType = new GraphQLObjectType({ }, ownership: { type: GraphQLBoolean, - description: - 'Limit domains to those that belong to an organization that has ownership.', + 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, - { loaders: { loadDomainConnectionsByOrgId } }, + + { dataSources: { auth: authDS }, loaders: { loadDomainConnectionsByOrgId } }, ) => { + const permission = await authDS.permissionByOrgId.load(_id) const connections = await loadDomainConnectionsByOrgId({ orgId: _id, + permission, ...args, }) return connections @@ -118,6 +338,10 @@ export const organizationType = new GraphQLObjectType({ 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 ( @@ -125,27 +349,33 @@ export const organizationType = new GraphQLObjectType({ args, { i18n, - auth: { checkPermission }, + auth: { loginRequiredBool }, + dataSources: { auth: authDS }, loaders: { loadAffiliationConnectionsByOrgId }, }, ) => { - const permission = await checkPermission({ orgId: _id }) - if (permission === 'admin' || permission === 'super_admin') { - const affiliations = await loadAffiliationConnectionsByOrgId({ - orgId: _id, - ...args, - }) - return affiliations + 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.`)) } - 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.', + description: 'Organization object containing information for a given Organization.', }) diff --git a/api/src/organization/queries/__tests__/find-my-organizations.test.js b/api/src/organization/queries/__tests__/find-my-organizations.test.js index fae14e0108..11ab3ea768 100644 --- a/api/src/organization/queries/__tests__/find-my-organizations.test.js +++ b/api/src/organization/queries/__tests__/find-my-organizations.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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -39,22 +40,21 @@ describe('given findMyOrganizationsQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'french', emailValidated: true, }) @@ -144,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 { @@ -173,8 +173,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -194,17 +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: { @@ -250,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.`]) }) }) }) @@ -289,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 { @@ -318,8 +314,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -339,17 +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: { @@ -375,8 +369,7 @@ describe('given findMyOrganizationsQuery', () => { id: toGlobalId('organization', orgTwo._key), slug: 'ne-pas-secretariat-conseil-tresor', acronym: 'NPSCT', - name: - 'Ne Pas Secrétariat du Conseil Trésor du Canada', + name: 'Ne Pas Secrétariat du Conseil Trésor du Canada', zone: 'NPFED', sector: 'NPTBS', country: 'Canada', @@ -396,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.`]) }) }) }) @@ -423,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 { @@ -455,8 +444,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -464,24 +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([ @@ -507,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 { @@ -539,8 +520,8 @@ describe('given findMyOrganizationsQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, auth: { @@ -548,24 +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/src/organization/queries/__tests__/find-organization-by-slug.test.js b/api/src/organization/queries/__tests__/find-organization-by-slug.test.js index 3c6c93c712..cfb39fda7a 100644 --- a/api/src/organization/queries/__tests__/find-organization-by-slug.test.js +++ b/api/src/organization/queries/__tests__/find-organization-by-slug.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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -41,16 +42,16 @@ describe('given findOrganizationBySlugQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { user = await collections.users.save({ @@ -119,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 @@ -137,8 +138,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -158,9 +159,13 @@ describe('given findOrganizationBySlugQuery', () => { 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, @@ -169,18 +174,16 @@ describe('given findOrganizationBySlugQuery', () => { auth: { loginRequiredBool: true }, i18n, }), - loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId( - { - query, - userKey: user._key, - cleanseInput, - auth: { loginRequiredBool: true }, - i18n, - }, - ), + loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId({ + query, + userKey: user._key, + cleanseInput, + auth: { loginRequiredBool: true }, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -199,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}.`]) }) }) }) @@ -229,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 @@ -247,8 +248,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -268,9 +269,13 @@ describe('given findOrganizationBySlugQuery', () => { 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, @@ -279,17 +284,15 @@ describe('given findOrganizationBySlugQuery', () => { auth: { loginRequiredBool: true }, i18n, }), - loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId( - { - query, - userKey: user._key, - cleanseInput, - i18n, - }, - ), + loadAffiliationConnectionsByOrgId: loadAffiliationConnectionsByOrgId({ + query, + userKey: user._key, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -308,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}.`]) }) }) }) @@ -333,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" @@ -352,8 +353,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -368,31 +369,23 @@ describe('given findOrganizationBySlugQuery', () => { 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 @@ -407,8 +400,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -423,24 +416,16 @@ describe('given findOrganizationBySlugQuery', () => { 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.`]) }) }) }) @@ -461,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" @@ -480,8 +465,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -496,31 +481,23 @@ describe('given findOrganizationBySlugQuery', () => { 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 @@ -535,8 +512,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -551,24 +528,16 @@ describe('given findOrganizationBySlugQuery', () => { 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 index a468ac6125..507de5032f 100644 --- a/api/src/organization/queries/find-my-organizations.js +++ b/api/src/organization/queries/find-my-organizations.js @@ -22,8 +22,15 @@ export const findMyOrganizations = { }, includeSuperAdminOrg: { type: GraphQLBoolean, - description: - 'Filter org list to either include or exclude the super admin org.', + 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, }, @@ -32,13 +39,8 @@ export const findMyOrganizations = { args, { userKey, - auth: { - checkSuperAdmin, - userRequired, - verifiedRequired, - loginRequiredBool, - }, - loaders: { loadOrgConnectionsByUserId }, + auth: { checkSuperAdmin, userRequired, verifiedRequired, loginRequiredBool }, + dataSources: { organization: organizationDS }, }, ) => { if (loginRequiredBool) { @@ -48,7 +50,7 @@ export const findMyOrganizations = { const isSuperAdmin = await checkSuperAdmin() - const orgConnections = await loadOrgConnectionsByUserId({ + const orgConnections = await organizationDS.connectionsByUserId({ isSuperAdmin, ...args, }) diff --git a/api/src/organization/queries/find-organization-by-slug.js b/api/src/organization/queries/find-organization-by-slug.js index 16ef398c82..90919afe5a 100644 --- a/api/src/organization/queries/find-organization-by-slug.js +++ b/api/src/organization/queries/find-organization-by-slug.js @@ -1,18 +1,17 @@ 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.', + 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.', + type: new GraphQLNonNull(Slug), + description: 'The slugified organization name you want to retrieve data for.', }, }, resolve: async ( @@ -21,13 +20,8 @@ export const findOrganizationBySlug = { { i18n, userKey, - auth: { - checkPermission, - userRequired, - verifiedRequired, - loginRequiredBool, - }, - loaders: { loadOrgBySlug }, + auth: { checkPermission, userRequired, verifiedRequired, loginRequiredBool }, + dataSources: { organization: organizationDS }, validators: { cleanseInput }, }, ) => { @@ -41,46 +35,24 @@ export const findOrganizationBySlug = { const orgSlug = cleanseInput(args.orgSlug) // Retrieve organization by slug - const org = await loadOrgBySlug.load(orgSlug) + 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.`), - ) + throw new Error(i18n._(t`No organization with the provided slug could be found.`)) } - if (loginRequiredBool) { - // Check user permission for organization access - const permission = await checkPermission({ orgId: org._id }) - - if (!['super_admin', 'admin', 'user'].includes(permission)) { - console.warn(`User ${userKey} could not retrieve organization.`) - throw new Error( - i18n._( - t`Permission Denied: Could not retrieve specified organization.`, - ), - ) - } - } else { - if (org.slugEN === 'super-admin' || org.slugFR === 'super-admin') { - // Check user permission for super-admin organization access - const permission = await checkPermission({ orgId: org._id }) + // Check user permission for organization access + const permission = await checkPermission({ orgId: org._id }) - if (!['super_admin', 'admin', 'user'].includes(permission)) { - console.warn(`User ${userKey} could not retrieve organization.`) - throw new Error( - i18n._( - t`Permission Denied: Could not retrieve specified organization.`, - ), - ) - } - } + 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}.`, - ) + 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 index 1b2380b3ac..c34648f5db 100644 --- a/api/src/organization/queries/index.js +++ b/api/src/organization/queries/index.js @@ -1,2 +1,3 @@ export * from './find-my-organizations' export * from './find-organization-by-slug' +export * from './get-all-organization-domain-statuses' diff --git a/api/src/organization/unions/__tests__/create-organization-union.test.js b/api/src/organization/unions/__tests__/create-organization-union.test.js index 0b0325e73a..cffa51ed7f 100644 --- a/api/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/src/organization/unions/__tests__/remove-organization-union.test.js b/api/src/organization/unions/__tests__/remove-organization-union.test.js index ad508809bc..4250db9aa7 100644 --- a/api/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/src/organization/unions/__tests__/update-organization-union.test.js b/api/src/organization/unions/__tests__/update-organization-union.test.js index f6178bb027..52e384b1b9 100644 --- a/api/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/src/organization/unions/__tests__/verify-organization-union.test.js b/api/src/organization/unions/__tests__/verify-organization-union.test.js index bfe92b7019..280ca20c9b 100644 --- a/api/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/src/organization/unions/create-organization-union.js b/api/src/organization/unions/create-organization-union.js index 4b3b19db2f..b1b13571f2 100644 --- a/api/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/src/organization/unions/remove-organization-union.js b/api/src/organization/unions/remove-organization-union.js index 527a197fac..ce045b77e8 100644 --- a/api/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/src/organization/unions/update-organization-union.js b/api/src/organization/unions/update-organization-union.js index 3ba080fe72..f6c35b23e3 100644 --- a/api/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/src/organization/unions/verify-organization-union.js b/api/src/organization/unions/verify-organization-union.js index 8ce160e57e..d804e256ed 100644 --- a/api/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/src/query.js b/api/src/query.js index 62979acd91..03430825eb 100644 --- a/api/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/src/scalars/__tests__/scalar-domain.test.js b/api/src/scalars/__tests__/scalar-domain.test.js index d2e2aee7ec..515a91114b 100644 --- a/api/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/src/scalars/__tests__/scalar-organization-acronym.test.js b/api/src/scalars/__tests__/scalar-organization-acronym.test.js index 67803df484..e9ff9c719e 100644 --- a/api/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' @@ -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}`), ) @@ -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}`), ) @@ -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 index 31016886c4..d55c224733 100644 --- a/api/src/scalars/__tests__/scalar-selector.test.js +++ b/api/src/scalars/__tests__/scalar-selector.test.js @@ -1,91 +1,76 @@ -import { Kind } from 'graphql' +import { GraphQLError, Kind } from 'graphql' import { stringify } from 'jest-matcher-utils' -import { Selectors } from '../index' +import { Selectors, SelectorsInput } 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' - expect(Selectors.serialize(testSelector)).toEqual(testSelector) +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 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.parseValue(invalidInput)).toThrow( + new TypeError(`Value is not a string: ${typeof invalidInput}`), ) }) }) }) }) - 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' - expect(Selectors.parseValue(testSelector)).toEqual(testSelector) + 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 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', () => { + ;[ + { + 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('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("checking a 'selectorInput' type", () => { + describe('serializing inputs', () => { 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) + 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' - const testLiteral = { - kind: Kind.STRING, - value: testSelector, - } - expect(() => Selectors.parseLiteral(testLiteral, {})).toThrow( + expect(() => SelectorsInput.serialize(testSelector)).toThrow( new TypeError(`Value is not a valid selector: ${testSelector}`), ) }) @@ -93,22 +78,10 @@ describe('given a selectors scalar', () => { }) }) 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}`, - ), + ;[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/src/scalars/__tests__/scalar-slug.test.js b/api/src/scalars/__tests__/scalar-slug.test.js index d179d6e7a1..5a024d33a4 100644 --- a/api/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/src/scalars/__tests__/scalar-year.test.js b/api/src/scalars/__tests__/scalar-year.test.js index 370ff37356..7ac751ed3a 100644 --- a/api/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 index dc4fd32123..c17891b124 100644 --- a/api/src/scalars/domain.js +++ b/api/src/scalars/domain.js @@ -7,8 +7,7 @@ const validate = (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/ + 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}`) @@ -25,9 +24,7 @@ export const Domain = new GraphQLScalarType({ parseLiteral(ast) { if (ast.kind !== Kind.STRING) { - throw new GraphQLError( - `Can only validate strings as domains but got a: ${ast.kind}`, - ) + throw new GraphQLError(`Can only validate strings as domains but got a: ${ast.kind}`) } return validate(ast.value) }, 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 index ea5b750222..47f08dd9fe 100644 --- a/api/src/scalars/index.js +++ b/api/src/scalars/index.js @@ -1,3 +1,4 @@ +export * from './cve-id' export * from './domain' export * from './organization-acronym' export * from './selector' diff --git a/api/src/scalars/organization-acronym.js b/api/src/scalars/organization-acronym.js index 97f01c50f4..bc874e9469 100644 --- a/api/src/scalars/organization-acronym.js +++ b/api/src/scalars/organization-acronym.js @@ -1,4 +1,4 @@ -import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' +import {Kind, GraphQLError, GraphQLScalarType} from 'graphql' const validate = (value) => { const ACRONYM_REGEX = /^[A-Za-z0-9_-]{1,50}$/ diff --git a/api/src/scalars/selector.js b/api/src/scalars/selector.js index 188f357248..b7c275875f 100644 --- a/api/src/scalars/selector.js +++ b/api/src/scalars/selector.js @@ -1,7 +1,29 @@ import { Kind, GraphQLError, GraphQLScalarType } from 'graphql' -const validate = (value) => { - const SLUG_REGEX = /^[a-zA-Z0-9](\.?[a-zA-Z0-9])*$/ +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}`) } @@ -12,19 +34,17 @@ const validate = (value) => { return value } -export const Selectors = new GraphQLScalarType({ - name: 'Selector', +export const SelectorsInput = new GraphQLScalarType({ + name: 'SelectorInput', description: - 'A field that conforms to a DKIM selector. Only alphanumeric characters and periods are allowed, string must also start and end with alphanumeric characters', - serialize: validate, - parseValue: validate, + '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}`, - ) + throw new GraphQLError(`Can only validate strings as selectors but got a: ${ast.kind}`) } - return validate(ast.value) + return validateSelectorInput(ast.value) }, }) diff --git a/api/src/scalars/slug.js b/api/src/scalars/slug.js index bd5d27faf0..f66e2ecd16 100644 --- a/api/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/src/scalars/year.js b/api/src/scalars/year.js index e27d969311..e5de802a24 100644 --- a/api/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 index 65d796402d..44a3a92742 100644 --- a/api/src/server.js +++ b/api/src/server.js @@ -1,34 +1,31 @@ import cookieParser from 'cookie-parser' import cors from 'cors' import express from 'express' +import { json } from 'body-parser' + import http from 'http' -import { ApolloServerPluginLandingPageGraphQLPlayground as enablePlayground } from 'apollo-server-core' -import { ApolloServer } from 'apollo-server-express' +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 { execute, subscribe, GraphQLSchema } from 'graphql' +import { 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 { createQuerySchema } from './query' import { createMutationSchema } from './mutation' -import { createSubscriptionSchema } from './subscription' +import { FilterValueScalar } from './scalars/filter-value' const createSchema = () => new GraphQLSchema({ query: createQuerySchema(), mutation: createMutationSchema(), - subscription: createSubscriptionSchema(), + types: [FilterValueScalar], }) -const createValidationRules = ( - maxDepth, - complexityCost, - scalarCost, - objectCost, - listFactor, -) => { +const createValidationRules = (maxDepth, complexityCost, scalarCost, objectCost, listFactor) => { return [ depthLimit(maxDepth), createComplexityLimitRule(complexityCost, { @@ -53,19 +50,51 @@ export const Server = async ({ context = {}, }) => { const app = express() - - app.use('*', cors()) - - app.use(cookieParser()) - + 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.use('/voyager', voyagerMiddleware({ endpointUrl: '/graphql' })) - app.get('/alive', (_req, res) => { res.json({ ok: 'yes' }) }) @@ -74,58 +103,5 @@ export const Server = async ({ 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, - validationRules: createValidationRules( - maxDepth, - complexityCost, - scalarCost, - objectCost, - listFactor, - ), - introspection: true, - tracing, - plugins: [enablePlayground()], - }) - - await server.start() - server.applyMiddleware({ app }) - - SubscriptionServer.create( - { - schema, - execute, - subscribe, - }, - { - server: httpServer, - path: server.graphqlPath, - }, - ) - return httpServer } diff --git a/api/src/subscription.js b/api/src/subscription.js deleted file mode 100644 index 23c2ed9d9f..0000000000 --- a/api/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/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 index 7dab4e57a0..fd864e4e36 100644 --- a/api/src/summaries/index.js +++ b/api/src/summaries/index.js @@ -1,3 +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/__tests__/load-chart-summary-by-key.test.js b/api/src/summaries/loaders/__tests__/load-chart-summary-by-key.test.js deleted file mode 100644 index 66738229a9..0000000000 --- a/api/src/summaries/loaders/__tests__/load-chart-summary-by-key.test.js +++ /dev/null @@ -1,226 +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 { loadChartSummaryByKey } from '../../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/summaries/loaders/index.js b/api/src/summaries/loaders/index.js index b06b337eb6..427499a436 100644 --- a/api/src/summaries/loaders/index.js +++ b/api/src/summaries/loaders/index.js @@ -1 +1 @@ -export * from './load-chart-summary-by-key' +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/src/summaries/loaders/load-chart-summary-by-key.js b/api/src/summaries/loaders/load-chart-summary-by-key.js deleted file mode 100644 index 25691a7c74..0000000000 --- a/api/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/src/summaries/objects/__tests__/categorized-summary.test.js b/api/src/summaries/objects/__tests__/categorized-summary.test.js index 0433930feb..0713d13c64 100644 --- a/api/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/src/summaries/objects/__tests__/summary-category.test.js b/api/src/summaries/objects/__tests__/summary-category.test.js index 0dc5220f43..d13b9d3bb8 100644 --- a/api/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/src/summaries/objects/categorized-summary.js b/api/src/summaries/objects/categorized-summary.js index 44c5fc1790..571ba841b9 100644 --- a/api/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 index e86c987e36..e4f1752751 100644 --- a/api/src/summaries/objects/index.js +++ b/api/src/summaries/objects/index.js @@ -1,2 +1,3 @@ export * from './categorized-summary' export * from './summary-category' +export * from './chart-summary' diff --git a/api/src/summaries/objects/summary-category.js b/api/src/summaries/objects/summary-category.js index e501bc6d86..4db4340082 100644 --- a/api/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__/dmarc-phase-summary.test.js b/api/src/summaries/queries/__tests__/dmarc-phase-summary.test.js deleted file mode 100644 index 7e28c6d722..0000000000 --- a/api/src/summaries/queries/__tests__/dmarc-phase-summary.test.js +++ /dev/null @@ -1,256 +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 { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { loadChartSummaryByKey } from '../../loaders' -import dbschema from '../../../../database.json' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given dmarcPhaseSummary 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 dmarc phase summary retrieval', () => { - 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 () => { - await collections.chartSummaries.save({ - _key: 'dmarc_phase', - not_implemented: 200, - assess: 200, - deploy: 200, - enforce: 200, - maintain: 200, - total: 1000, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - it('returns dmarc phase summary', async () => { - const response = await graphql( - schema, - ` - query { - dmarcPhaseSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: loadChartSummaryByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - dmarcPhaseSummary: { - total: 1000, - categories: [ - { - name: 'not implemented', - count: 200, - percentage: 20, - }, - { - name: 'assess', - count: 200, - percentage: 20, - }, - { - name: 'deploy', - count: 200, - percentage: 20, - }, - { - name: 'enforce', - count: 200, - percentage: 20, - }, - { - name: 'maintain', - count: 200, - percentage: 20, - }, - ], - }, - }, - } - 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 dmarc phase summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - dmarcPhaseSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - `Unable to load DMARC phase summary. Please try again.`, - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve DMARC phase 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 dmarc phase summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - dmarcPhaseSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de charger le résumé DMARC. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve DMARC phase summary.`, - ]) - }) - }) - }) - }) -}) 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/__tests__/https-summary.test.js b/api/src/summaries/queries/__tests__/https-summary.test.js deleted file mode 100644 index a80cfb57df..0000000000 --- a/api/src/summaries/queries/__tests__/https-summary.test.js +++ /dev/null @@ -1,236 +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 { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { loadChartSummaryByKey } from '../../loaders' -import dbschema from '../../../../database.json' - -const { DB_PASS: rootPass, DB_URL: url } = process.env - -describe('given httpsSummary 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 https summary retrieval', () => { - 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 () => { - await collections.chartSummaries.save({ - _key: 'https', - total: 1000, - fail: 500, - pass: 500, - }) - }) - afterEach(async () => { - await truncate() - }) - afterAll(async () => { - await drop() - }) - it('returns https summary', async () => { - const response = await graphql( - schema, - ` - query { - httpsSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: loadChartSummaryByKey({ query }), - }, - }, - ) - - const expectedResponse = { - data: { - httpsSummary: { - 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 https summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - httpsSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError(`Unable to load HTTPS summary. Please try again.`), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve HTTPS 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 https summary retrieval', () => { - describe('summary cannot be found', () => { - it('returns an appropriate error message', async () => { - const response = await graphql( - schema, - ` - query { - httpsSummary { - total - categories { - name - count - percentage - } - } - } - `, - null, - { - i18n, - loaders: { - loadChartSummaryByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de charger le résumé HTTPS. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `User could not retrieve HTTPS summary.`, - ]) - }) - }) - }) - }) -}) diff --git a/api/src/summaries/queries/__tests__/mail-summary.test.js b/api/src/summaries/queries/__tests__/mail-summary.test.js deleted file mode 100644 index d095844cde..0000000000 --- a/api/src/summaries/queries/__tests__/mail-summary.test.js +++ /dev/null @@ -1,236 +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 { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { loadChartSummaryByKey } from '../../loaders' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/summaries/queries/__tests__/web-summary.test.js b/api/src/summaries/queries/__tests__/web-summary.test.js deleted file mode 100644 index 8decacb540..0000000000 --- a/api/src/summaries/queries/__tests__/web-summary.test.js +++ /dev/null @@ -1,235 +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 { createQuerySchema } from '../../../query' -import { createMutationSchema } from '../../../mutation' -import { loadChartSummaryByKey } from '../../loaders' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/summaries/queries/dmarc-phase-summary.js b/api/src/summaries/queries/dmarc-phase-summary.js deleted file mode 100644 index 1b985b5450..0000000000 --- a/api/src/summaries/queries/dmarc-phase-summary.js +++ /dev/null @@ -1,57 +0,0 @@ -import { categorizedSummaryType } from '../objects' -import { t } from '@lingui/macro' - -export const dmarcPhaseSummary = { - type: categorizedSummaryType, - description: - 'DMARC phase summary computed values, used to build summary cards.', - resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { - const summary = await loadChartSummaryByKey.load('dmarc_phase') - - if (typeof summary === 'undefined') { - console.warn(`User could not retrieve DMARC phase summary.`) - throw new Error( - i18n._(t`Unable to load DMARC phase summary. Please try again.`), - ) - } - - const categories = [ - { - name: 'not implemented', - count: summary.not_implemented, - percentage: Number( - ((summary.not_implemented / summary.total) * 100).toFixed(1), - ), - }, - { - name: 'assess', - count: summary.assess, - percentage: Number(((summary.assess / summary.total) * 100).toFixed(1)), - }, - { - name: 'deploy', - count: summary.deploy, - percentage: Number(((summary.deploy / summary.total) * 100).toFixed(1)), - }, - { - name: 'enforce', - count: summary.enforce, - percentage: Number( - ((summary.enforce / summary.total) * 100).toFixed(1), - ), - }, - { - name: 'maintain', - count: summary.maintain, - percentage: Number( - ((summary.maintain / summary.total) * 100).toFixed(1), - ), - }, - ] - - return { - categories, - total: summary.total, - } - }, -} 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/https-summary.js b/api/src/summaries/queries/https-summary.js deleted file mode 100644 index aaa025f1e8..0000000000 --- a/api/src/summaries/queries/https-summary.js +++ /dev/null @@ -1,35 +0,0 @@ -import { categorizedSummaryType } from '../objects' -import { t } from '@lingui/macro' - -export const httpsSummary = { - type: categorizedSummaryType, - description: 'HTTPS summary computed values, used to build summary cards.', - resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { - const summary = await loadChartSummaryByKey.load('https') - - if (typeof summary === 'undefined') { - console.warn(`User could not retrieve HTTPS summary.`) - throw new Error( - i18n._(t`Unable to load HTTPS 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/src/summaries/queries/index.js b/api/src/summaries/queries/index.js index 6b51658f0f..8002030019 100644 --- a/api/src/summaries/queries/index.js +++ b/api/src/summaries/queries/index.js @@ -1,4 +1 @@ -export * from './mail-summary' -export * from './web-summary' -export * from './dmarc-phase-summary' -export * from './https-summary' +export * from './find-chart-summaries' diff --git a/api/src/summaries/queries/mail-summary.js b/api/src/summaries/queries/mail-summary.js deleted file mode 100644 index a43eb8d5ce..0000000000 --- a/api/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/src/summaries/queries/web-summary.js b/api/src/summaries/queries/web-summary.js deleted file mode 100644 index 2079b5d88b..0000000000 --- a/api/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/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..7df1884dcb --- /dev/null +++ b/api/src/testUtilities.js @@ -0,0 +1,63 @@ +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 + } + + const ensured = await ensure(ensureOptions) + + const db = new Database({ url: variables.url, databaseName: variables.name }) + await db.login(variables.username || 'root', variables.password || variables.rootPassword) + + return { ...ensured, db } +} + +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 index 1b5bd9dcd0..ee648b9915 100644 --- a/api/src/user/index.js +++ b/api/src/user/index.js @@ -3,3 +3,4 @@ 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/src/user/loaders/__tests__/load-user-by-key.test.js b/api/src/user/loaders/__tests__/load-user-by-key.test.js index 24646c2b61..ee7f164734 100644 --- a/api/src/user/loaders/__tests__/load-user-by-key.test.js +++ b/api/src/user/loaders/__tests__/load-user-by-key.test.js @@ -1,4 +1,5 @@ -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' @@ -35,29 +36,27 @@ describe('given a loadUserByKey dataloader', () => { 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, - })) + 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, }) @@ -123,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', @@ -135,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([ @@ -162,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([ @@ -190,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', @@ -202,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([ @@ -231,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/src/user/loaders/__tests__/load-user-by-username.test.js b/api/src/user/loaders/__tests__/load-user-by-username.test.js index 7fac9db296..e08aa6280d 100644 --- a/api/src/user/loaders/__tests__/load-user-by-username.test.js +++ b/api/src/user/loaders/__tests__/load-user-by-username.test.js @@ -1,4 +1,5 @@ -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' @@ -34,29 +35,27 @@ describe('given a loadUserByUserName dataloader', () => { 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, - })) + 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, }) @@ -87,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) { @@ -128,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([ @@ -161,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([ @@ -191,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([ @@ -226,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 index 1df3a835e9..c55f567bd7 100644 --- a/api/src/user/loaders/index.js +++ b/api/src/user/loaders/index.js @@ -1,2 +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/src/user/loaders/load-user-by-key.js b/api/src/user/loaders/load-user-by-key.js index a764137ba7..b85af65a1f 100644 --- a/api/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/src/user/loaders/load-user-by-username.js b/api/src/user/loaders/load-user-by-username.js index de9697827e..b0c9a5d0a4 100644 --- a/api/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/src/user/mutations/__tests__/authenticate.test.js b/api/src/user/mutations/__tests__/authenticate.test.js index d630ef5494..94235d3ac7 100644 --- a/api/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' @@ -13,47 +14,19 @@ 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('authenticate user account', () => { let query, drop, truncate, schema, collections, transaction, mockTokenize @@ -101,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, @@ -128,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}" } @@ -145,7 +118,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -158,8 +130,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, @@ -177,7 +149,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -188,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, }, }, }, @@ -215,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', () => { @@ -225,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, @@ -252,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}" } @@ -269,7 +251,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -282,16 +263,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, response: mockedResponse, + jwt, auth: { bcrypt, - tokenize: mockTokenize, + tokenize: mockedTokenize, verifyToken: verifyToken({}), }, validators: { @@ -301,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, }, }, }, @@ -332,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.`]) }) }) }) @@ -368,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}" } @@ -385,7 +365,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -398,8 +377,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -417,7 +396,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { @@ -431,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', () => { @@ -442,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}" } @@ -459,7 +437,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -472,8 +449,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -491,7 +468,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const error = { data: { @@ -505,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', () => { @@ -516,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}" } @@ -533,7 +509,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -546,8 +521,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -567,7 +542,7 @@ describe('authenticate user account', () => { }, }, }, - ) + }) const error = { data: { @@ -581,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}" } @@ -609,7 +603,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -622,8 +615,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -645,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([ @@ -665,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}" } @@ -682,7 +674,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -695,15 +686,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -726,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([ @@ -747,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}" } @@ -764,7 +753,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -777,16 +765,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -809,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([ @@ -844,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}" } @@ -861,7 +847,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -874,8 +859,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -893,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', () => { @@ -919,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}" } @@ -936,7 +919,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -949,8 +931,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -968,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', () => { @@ -994,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}" } @@ -1011,7 +991,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -1024,8 +1003,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1045,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}" } @@ -1088,7 +1085,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -1101,8 +1097,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1124,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([ @@ -1144,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}" } @@ -1161,7 +1156,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -1174,15 +1168,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1205,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([ @@ -1228,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}" } @@ -1245,7 +1235,6 @@ describe('authenticate user account', () => { id userName displayName - preferredLang phoneValidated emailValidated } @@ -1258,16 +1247,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1290,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 index aefe637924..500f06a53e 100644 --- a/api/src/user/mutations/__tests__/close-account.test.js +++ b/api/src/user/mutations/__tests__/close-account.test.js @@ -1,5 +1,6 @@ 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' @@ -12,53 +13,12 @@ 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('given the closeAccount mutation', () => { - let i18n, - query, - drop, - truncate, - schema, - collections, - transaction, - user, - org, - domain + let i18n, query, drop, truncate, schema, collections, transaction, user, org, domain const consoleOutput = [] const mockedConsole = (output) => consoleOutput.push(output) @@ -143,38 +103,27 @@ describe('given the closeAccount mutation', () => { _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, }) @@ -183,3887 +132,591 @@ describe('given the closeAccount mutation', () => { _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, - }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', }) - 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 - } - } + }) + it('removes the users affiliations', async () => { + await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } - `, - null, - { + } + } + `, + 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, - collections: collectionNames, - transaction, + }), + userRequired: userRequired({ + 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, - }), - }), - }, - 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` + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + }) - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).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('removes ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } + }) + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, + } + `, + 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, - }), - 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 }, + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), }, - ) + validators: { cleanseInput }, + }, + }) - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const expectedResponse = { + data: { + closeAccountSelf: { + result: { + status: 'Successfully closed account.', + }, + }, + }, + } - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) }) - describe('org is not owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) + }) + 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('does not remove dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } + }) + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, + } + `, + 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, - }), - userRequired: userRequired({ - i18n, userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), + 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() + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, }) - it('does not remove ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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, - }), + + const expectedResponse = { + data: { + closeAccountSelf: { + result: { + status: 'Le compte a été fermé avec succès.', }, - 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() - }) + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) }) - 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 - } - } + }) + it('closes the users account', async () => { + await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - 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 - } - } + ... on CloseAccountError { + code + description } } - `, - null, - { + } + } + `, + rootValue: null, + contextValue: { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, + auth: { + checkSuperAdmin: checkSuperAdmin({ i18n, - query, - collections: collectionNames, - 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, - { + query, + }), + userRequired: userRequired({ i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, - collections: collectionNames, - transaction, + language: 'en', + 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, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: { cleanseInput }, - }, - ) + }), + }, + validators: { cleanseInput }, + }, + }) - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` + await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - 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: collectionNames, - 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 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, + }) - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ + _from: domain._id, + _to: dns._id, + }) - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toEqual(undefined) - }) + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ + _from: domain._id, + _to: web._id, }) - 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: collectionNames, - 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 webScan = await collections.webScan.save({ + webScan: true, + }) + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, + }) - 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 - } - } + 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 } } - `, - null, - { + } + } + `, + 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, - collections: collectionNames, - transaction, + }), + userRequired: userRequired({ + 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, - }), - }), - }, - 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: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, + loadUserByKey: loadUserByKey({ 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, + loaders: { + loadOrgByKey: loadOrgByKey({ query, - collections: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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, + 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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, - { + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, i18n, - query: mockedQuery, - collections: collectionNames, - 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 }, - }, - ) + }), + }, + validators: { cleanseInput }, + }, + }) - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - 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`, - ]) + 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, + }, }) }) - 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 - } + it('returns a status message', async () => { + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ + } + `, + 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, - language: 'en', + userKey: user._key, i18n, - userKey: '123', }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, - }, - validators: { cleanseInput }, + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), }, - ) + validators: { cleanseInput }, + }, + }) - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] + const expectedResponse = { + data: { + closeAccountSelf: { + result: { + status: 'Successfully closed account.', + }, + }, + }, + } - 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`, - ]) - }) + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) }) }) - 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')), + 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, + }, }) - - const response = await graphql( + }) + it('returns a status message', async () => { + const response = await graphql({ schema, - ` + source: ` mutation { - closeAccount(input: {}) { + closeAccountSelf(input: {}) { result { ... on CloseAccountResult { status @@ -4076,49 +729,135 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', + transaction, + userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), + 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: '123', + userKey: user._key, + }), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, }, 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 }, + }, + }) - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] + await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - 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`, - ]) - }) + 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('language is set to french', () => { + }) + + describe('given an unsuccessful closing of an account', () => { + describe('language is set to english', () => { beforeAll(() => { i18n = setupI18n({ - locale: 'fr', + locale: 'en', localeData: { en: { plurals: {} }, fr: { plurals: {} }, @@ -4133,11 +872,11 @@ describe('given the closeAccount mutation', () => { 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( + const response = await graphql({ schema, - ` + source: ` mutation { - closeAccount(input: { + closeAccountOther(input:{ userId: "${toGlobalId('user', '456')}" }) { result { @@ -4152,13 +891,14 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + 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' }), @@ -4173,323 +913,34 @@ describe('given the closeAccount mutation', () => { }, 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: collectionNames, - 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: { + closeAccountOther: { result: { code: 400, - description: - "Impossible de fermer le compte d'un utilisateur non défini.", + 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 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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( + 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 { - closeAccount(input: {}) { + closeAccountOther(input:{ + userId: "${toGlobalId('user', '456')}" + }) { result { ... on CloseAccountResult { status @@ -4502,18 +953,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, + transaction, userKey: '123', + request: { ip: '127.0.0.1' }, auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -4523,46 +973,51 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), + load: jest.fn().mockReturnValue(undefined), }, }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const expectedResponse = { + data: { + closeAccountOther: { + result: { + code: 400, + description: 'Unable to close account of an undefined user.', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `Cursor error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: cursor error`, + `User: 123 attempted to close user: 456 account, but requested user is undefined.`, ]) }) }) - describe('when getting ownership info', () => { + }) + describe('trx step error occurs', () => { + describe('when removing the users affiliations', () => { it('throws an error', async () => { const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), + all: jest.fn().mockReturnValue([{ count: 2 }]), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), + step: jest.fn().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { - closeAccount(input: {}) { + closeAccountSelf(input: {}) { result { ... on CloseAccountResult { status @@ -4575,18 +1030,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + 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' }), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -4601,42 +1055,35 @@ describe('given the closeAccount mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + 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`, + `Trx step error occurred when removing users remaining affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, ]) }) }) - describe('when getting claim info', () => { + describe('when removing the user', () => { it('throws an error', async () => { const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), + all: jest.fn().mockReturnValue([{ count: 2 }]), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { - closeAccount(input: {}) { + closeAccountSelf(input: {}) { result { ... on CloseAccountResult { status @@ -4649,18 +1096,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + 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' }), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -4675,494 +1121,109 @@ describe('given the closeAccount mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + 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`, + `Trx step error occurred when removing user: 123 attempted to close account: users/123: Error: trx step 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: collectionNames, - 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 }, - }, - ) + describe('trx commit error occurs', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - 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`, - ]) - }) + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + abort: jest.fn(), }) - 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 - } - } + const response = await graphql({ + schema, + source: ` + mutation { + closeAccountSelf(input: {}) { + result { + ... on CloseAccountResult { + status } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - 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 - } - } + ... on CloseAccountError { + code + description } } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - 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, - { + `, + 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, - query: mockedQuery, - collections: collectionNames, - 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 }, + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), }, - ) - - 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`, - ]) - }) + }, + validators: { cleanseInput }, + }, }) - 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: collectionNames, - 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.', - ), - ] + 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`, - ]) - }) - }) + 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('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: collectionNames, - 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('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 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( + }) + 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 { - closeAccount(input: {}) { + closeAccountOther(input:{ + userId: "${toGlobalId('user', '456')}" + }) { result { ... on CloseAccountResult { status @@ -5175,18 +1236,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, + transaction, userKey: '123', + request: { ip: '127.0.0.1' }, auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), + checkSuperAdmin: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5195,49 +1255,37 @@ describe('given the closeAccount mutation', () => { i18n, userKey: '123', }), - loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), - }, }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const expectedResponse = { + data: { + closeAccountOther: { + result: { + code: 400, + description: "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.", + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, + `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, ]) }) }) - 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( + describe('requested user is undefined', () => { + it('returns an error', async () => { + const response = await graphql({ schema, - ` + source: ` mutation { - closeAccount(input: {}) { + closeAccountOther(input:{ + userId: "${toGlobalId('user', '456')}" + }) { result { ... on CloseAccountResult { status @@ -5250,18 +1298,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, + transaction, userKey: '123', + request: { ip: '127.0.0.1' }, auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({ _key: '123', _id: 'users/123' }), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5271,25 +1318,32 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({ _key: '123' }), + load: jest.fn().mockReturnValue(undefined), }, }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const expectedResponse = { + data: { + closeAccountOther: { + result: { + code: 400, + description: "Impossible de fermer le compte d'un utilisateur non défini.", + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(expectedResponse) 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`, + `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 = { @@ -5299,22 +1353,16 @@ describe('given the closeAccount mutation', () => { 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')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { - closeAccount(input: {}) { + closeAccountSelf(input: {}) { result { ... on CloseAccountResult { status @@ -5327,18 +1375,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + 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' }), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5353,13 +1400,9 @@ describe('given the closeAccount mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -5376,23 +1419,16 @@ describe('given the closeAccount mutation', () => { 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')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), + abort: jest.fn(), }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { - closeAccount(input: {}) { + closeAccountSelf(input: {}) { result { ... on CloseAccountResult { status @@ -5405,18 +1441,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + 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' }), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5431,13 +1466,9 @@ describe('given the closeAccount mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -5457,13 +1488,14 @@ describe('given the closeAccount 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 { - closeAccount(input: {}) { + closeAccountSelf(input: {}) { result { ... on CloseAccountResult { status @@ -5476,18 +1508,17 @@ describe('given the closeAccount mutation', () => { } } `, - null, - { + 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' }), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5502,13 +1533,9 @@ describe('given the closeAccount mutation', () => { }, validators: { cleanseInput }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ 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/src/user/mutations/__tests__/refresh-tokens.test.js b/api/src/user/mutations/__tests__/refresh-tokens.test.js index d4e8194d1d..c726fdb358 100644 --- a/api/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' @@ -12,47 +13,18 @@ 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('refresh users tokens', () => { let query, drop, truncate, schema, collections, transaction, user @@ -99,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, @@ -130,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 { @@ -150,8 +121,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, @@ -170,7 +141,7 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -192,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', () => { @@ -202,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, @@ -214,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') @@ -233,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 { @@ -253,8 +229,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, @@ -264,7 +240,7 @@ describe('refresh users tokens', () => { request: mockedRequest, response: mockedResponse, auth: { - tokenize: jest.fn().mockReturnValue('token'), + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -273,13 +249,13 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { refreshTokens: { result: { - authToken: 'token', + authToken: authToken, user: { displayName: 'Test Account', }, @@ -289,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.`]) }) }) }) @@ -331,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 { @@ -351,8 +325,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -371,7 +345,7 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -385,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', () => { @@ -407,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 { @@ -427,8 +399,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -447,7 +419,7 @@ describe('refresh users tokens', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -483,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 { @@ -503,8 +475,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -525,7 +497,7 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { @@ -539,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', () => { @@ -561,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 { @@ -581,8 +551,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -607,7 +577,7 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { @@ -621,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', () => { @@ -643,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 { @@ -663,8 +631,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -689,7 +657,7 @@ describe('refresh users tokens', () => { }, }, }, - ) + }) const expectedResult = { data: { @@ -703,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.`]) }) }) }) @@ -713,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({ @@ -733,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 { @@ -753,8 +718,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -780,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([ @@ -798,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({ @@ -819,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 { @@ -839,8 +801,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -866,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([ @@ -909,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 { @@ -929,8 +889,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -949,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', () => { @@ -986,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 { @@ -1006,8 +963,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1026,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.', }, }, }, @@ -1063,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 { @@ -1083,8 +1039,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1105,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', () => { @@ -1142,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 { @@ -1162,8 +1115,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1188,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', () => { @@ -1225,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 { @@ -1245,8 +1195,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1271,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.`]) }) }) }) @@ -1296,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({ @@ -1316,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 { @@ -1336,8 +1282,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1363,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([ @@ -1383,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({ @@ -1404,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 { @@ -1424,8 +1365,8 @@ describe('refresh users tokens', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1451,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/src/user/mutations/__tests__/remove-phone-number.test.js b/api/src/user/mutations/__tests__/remove-phone-number.test.js index 862bce1748..ab123372a8 100644 --- a/api/src/user/mutations/__tests__/remove-phone-number.test.js +++ b/api/src/user/mutations/__tests__/remove-phone-number.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' @@ -9,42 +10,10 @@ 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('testing the removePhoneNumber mutation', () => { let query, drop, truncate, schema, i18n, collections, transaction, user @@ -118,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 { @@ -135,8 +104,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -148,7 +117,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -161,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 { @@ -183,8 +150,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -196,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 { @@ -222,8 +187,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -235,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 { @@ -261,8 +224,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -274,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') }) @@ -298,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 { @@ -315,8 +276,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -328,7 +289,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -341,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 { @@ -363,8 +322,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -376,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 { @@ -402,8 +359,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -415,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 { @@ -441,8 +396,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -454,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') }) @@ -494,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 { @@ -511,8 +464,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -524,7 +477,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -537,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 { @@ -559,8 +510,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -572,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 { @@ -598,8 +547,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -611,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 { @@ -637,8 +584,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -650,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') }) @@ -674,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 { @@ -691,8 +636,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -704,7 +649,7 @@ describe('testing the removePhoneNumber mutation', () => { }), }, }, - ) + }) const expectedResponse = { data: { @@ -717,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 { @@ -739,8 +682,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -752,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 { @@ -778,8 +719,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -791,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 { @@ -817,8 +756,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -830,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') }) @@ -862,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 { @@ -886,8 +820,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -896,13 +830,9 @@ describe('testing the removePhoneNumber mutation', () => { 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([ @@ -916,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 { @@ -940,8 +867,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -950,13 +877,9 @@ describe('testing the removePhoneNumber mutation', () => { 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([ @@ -985,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 { @@ -1009,8 +929,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -1019,13 +939,9 @@ describe('testing the removePhoneNumber mutation', () => { 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([ @@ -1039,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 { @@ -1063,8 +976,8 @@ describe('testing the removePhoneNumber mutation', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, collections: collectionNames, query, @@ -1073,13 +986,9 @@ describe('testing the removePhoneNumber mutation', () => { 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/src/user/mutations/__tests__/reset-password.test.js b/api/src/user/mutations/__tests__/reset-password.test.js index 5d65b6edbe..366aa1b79b 100644 --- a/api/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' @@ -13,42 +14,10 @@ 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 collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - const mockNotify = jest.fn() describe('reset users password', () => { @@ -87,9 +56,9 @@ describe('reset users password', () => { })) }) beforeEach(async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -97,21 +66,19 @@ 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: collectionNames, transaction, @@ -133,9 +100,10 @@ describe('reset users password', () => { request: { protocol: 'https', get: (text) => text, + ip: '127.0.0.1', }, }, - ) + }) }) afterEach(async () => { await truncate() @@ -170,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: { @@ -193,8 +161,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -212,7 +180,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -225,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: { @@ -254,8 +220,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -263,6 +229,7 @@ describe('reset users password', () => { jwt, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: jest.fn().mockReturnValue('token'), @@ -277,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` @@ -307,9 +273,9 @@ describe('reset users password', () => { parameters: { userKey: user._key, currentPassword: user.password }, }) - await graphql( + await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -330,12 +296,13 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -349,7 +316,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const checkCursor = await query` FOR user IN users @@ -388,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: { @@ -411,8 +378,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -430,7 +397,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -443,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: { @@ -472,8 +437,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -481,6 +446,7 @@ describe('reset users password', () => { uuidv4, response: mockedResponse, jwt, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: jest.fn().mockReturnValue('token'), @@ -495,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` @@ -525,9 +490,9 @@ describe('reset users password', () => { parameters: { userKey: user._key, currentPassword: user.password }, }) - await graphql( + await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -548,8 +513,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -567,7 +532,7 @@ describe('reset users password', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const checkCursor = await query` FOR user IN users @@ -602,9 +567,9 @@ describe('reset users password', () => { parameters: {}, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -625,8 +590,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -645,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.', }, }, }, @@ -671,9 +635,9 @@ describe('reset users password', () => { parameters: { userKey: undefined }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -694,8 +658,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -714,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.', }, }, }, @@ -740,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: { @@ -763,8 +726,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -783,7 +746,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -802,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: collectionNames, - 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: { @@ -906,8 +794,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -929,7 +817,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -954,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: { @@ -977,8 +865,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1000,7 +888,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -1029,9 +917,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1052,15 +940,14 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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, @@ -1079,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([ @@ -1102,9 +987,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1125,16 +1010,15 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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, @@ -1153,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([ @@ -1188,9 +1070,9 @@ describe('reset users password', () => { parameters: {}, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1211,8 +1093,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1231,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.', }, }, }, @@ -1257,9 +1138,9 @@ describe('reset users password', () => { parameters: { userKey: undefined }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1280,8 +1161,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1300,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.', }, }, }, @@ -1326,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: { @@ -1349,8 +1229,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1369,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: collectionNames, - 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.', }, }, }, @@ -1460,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.`, ]) }) }) @@ -1470,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: { @@ -1493,8 +1297,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1516,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.', }, }, }, @@ -1542,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: { @@ -1565,8 +1368,8 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1588,7 +1391,7 @@ describe('reset users password', () => { }, }, }, - ) + }) const error = { data: { @@ -1617,9 +1420,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1640,15 +1443,14 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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, @@ -1667,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([ @@ -1692,9 +1490,9 @@ describe('reset users password', () => { }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { resetPassword ( input: { @@ -1715,16 +1513,15 @@ describe('reset users password', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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, @@ -1743,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/src/user/mutations/__tests__/send-email-verification.test.js b/api/src/user/mutations/__tests__/send-email-verification.test.js deleted file mode 100644 index 1225ec511b..0000000000 --- a/api/src/user/mutations/__tests__/send-email-verification.test.js +++ /dev/null @@ -1,369 +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 { 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() - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/user/mutations/__tests__/send-password-reset.test.js b/api/src/user/mutations/__tests__/send-password-reset.test.js index f175eaf049..9fe11e1184 100644 --- a/api/src/user/mutations/__tests__/send-password-reset.test.js +++ b/api/src/user/mutations/__tests__/send-password-reset.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 } from 'graphql' import { setupI18n } from '@lingui/core' @@ -16,7 +17,6 @@ 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,16 +42,16 @@ describe('user send password reset email', () => { describe('successfully sends password reset email', () => { beforeAll(async () => { ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) afterEach(async () => { await truncate() @@ -79,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" } @@ -96,8 +95,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -115,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.', }, }, } @@ -135,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.`]) }) }) }) @@ -169,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" } @@ -186,8 +179,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -205,7 +198,7 @@ describe('user send password reset email', () => { sendPasswordResetEmail: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -225,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.`]) }) }) }) @@ -257,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: { @@ -270,8 +259,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -291,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.', }, }, } @@ -325,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: { @@ -338,8 +326,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, query, @@ -359,7 +347,7 @@ describe('user send password reset email', () => { sendPasswordResetEmail: mockNotify, }, }, - ) + }) const expectedResult = { data: { diff --git a/api/src/user/mutations/__tests__/set-phone-number.test.js b/api/src/user/mutations/__tests__/set-phone-number.test.js index 51715ffdd1..2c4e154b9e 100644 --- a/api/src/user/mutations/__tests__/set-phone-number.test.js +++ b/api/src/user/mutations/__tests__/set-phone-number.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' @@ -11,43 +12,11 @@ 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() -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('user sets a new phone number', () => { let query, drop, truncate, collections, transaction, schema, request, i18n const consoleOutput = [] @@ -112,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 { @@ -139,8 +107,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -163,10 +131,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -190,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) }) }) @@ -205,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, @@ -215,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 { @@ -235,8 +199,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -259,10 +223,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -286,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 { @@ -318,8 +277,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -342,10 +301,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -355,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 { @@ -375,8 +334,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -399,10 +358,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -416,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, @@ -426,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 { @@ -446,8 +404,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -470,10 +428,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -497,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 { @@ -529,8 +482,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -553,10 +506,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -566,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 { @@ -586,8 +539,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -610,10 +563,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -627,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, @@ -637,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 { @@ -657,8 +609,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -681,10 +633,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -708,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 { @@ -740,8 +687,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -764,10 +711,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -777,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 { @@ -797,8 +744,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -821,10 +768,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -855,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 { @@ -882,8 +828,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -906,10 +852,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -933,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) }) }) @@ -948,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, @@ -958,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 { @@ -978,8 +920,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1002,10 +944,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -1029,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 { @@ -1061,8 +998,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1085,10 +1022,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1098,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 { @@ -1118,8 +1055,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1142,10 +1079,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1159,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, @@ -1169,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 { @@ -1189,8 +1125,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1213,10 +1149,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -1240,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 { @@ -1272,8 +1203,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1296,10 +1227,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1309,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 { @@ -1329,8 +1260,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1353,10 +1284,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1370,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, @@ -1380,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 { @@ -1400,8 +1330,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1424,10 +1354,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResult = { data: { @@ -1451,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 { @@ -1483,8 +1408,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1507,10 +1432,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1520,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 { @@ -1540,8 +1465,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -1564,10 +1489,10 @@ describe('user sets a new phone number', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendTfaTextMsg: mockNotify, + sendAuthTextMsg: mockNotify, }, }, - ) + }) user = await loadUserByUserName({ query, userKey: '1', @@ -1601,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 { @@ -1626,8 +1550,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, @@ -1651,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([ @@ -1674,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 { @@ -1699,8 +1620,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, @@ -1722,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([ @@ -1760,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 { @@ -1785,8 +1703,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, @@ -1810,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([ @@ -1835,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 { @@ -1860,8 +1773,8 @@ describe('user sets a new phone number', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, @@ -1883,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/src/user/mutations/__tests__/sign-in.test.js b/api/src/user/mutations/__tests__/sign-in.test.js index 4c85aaab64..933c264e7c 100644 --- a/api/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' @@ -12,46 +13,23 @@ import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { loadUserByUserName } from '../../loaders' import dbschema from '../../../../database.json' - -const { DB_PASS: rootPass, DB_URL: url, REFRESH_TOKEN_EXPIRY } = process.env - -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] +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, transaction + let query, drop, truncate, schema, i18n, transaction const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) const mockedWarn = (output) => consoleOutput.push(output) @@ -66,7 +44,6 @@ describe('authenticate user account', () => { query: createQuerySchema(), mutation: createMutationSchema(), }) - tokenize = jest.fn().mockReturnValue('token') }) afterEach(() => { consoleOutput.length = 0 @@ -85,12 +62,11 @@ describe('authenticate user account', () => { schema: dbschema, })) - tokenize = jest.fn().mockReturnValue('token') }) beforeEach(async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -98,21 +74,19 @@ 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: collectionNames, transaction, @@ -134,9 +108,10 @@ describe('authenticate user account', () => { request: { protocol: 'https', get: (text) => text, + ip: '127.0.0.1', }, }, - ) + }) }) afterEach(async () => { await truncate() @@ -159,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` @@ -173,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: { @@ -195,16 +271,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -216,14 +293,14 @@ describe('authenticate user account', () => { sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'text', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -240,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', () => { @@ -259,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: { @@ -281,16 +364,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -302,14 +386,14 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'email', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -324,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', () => { @@ -347,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: { @@ -369,17 +464,19 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, + jwt, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -391,13 +488,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -411,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', () => { @@ -443,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: { @@ -466,17 +570,19 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, + jwt, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -488,13 +594,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -508,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.`]) }) }) }) @@ -538,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: { @@ -560,13 +660,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -581,7 +682,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) cursor = await query` FOR user IN users @@ -623,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: { @@ -649,16 +758,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -670,14 +780,14 @@ describe('authenticate user account', () => { sendAuthTextMsg: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'text', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -692,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', () => { @@ -711,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: { @@ -737,16 +853,17 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -758,14 +875,14 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { sendMethod: 'email', - authenticateToken: 'token', + authenticateToken: authToken, }, }, }, @@ -780,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', () => { @@ -803,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: { @@ -825,17 +953,18 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -847,13 +976,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -867,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', () => { @@ -899,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: { @@ -922,17 +1058,19 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, + jwt, auth: { bcrypt, - tokenize, + tokenize: mockedTokenize, }, validators: { cleanseInput, @@ -944,13 +1082,13 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const expectedResponse = { data: { signIn: { result: { - authToken: 'token', + authToken: authToken, }, }, }, @@ -964,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.`]) }) }) }) @@ -994,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: { @@ -1020,8 +1152,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1041,7 +1173,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) cursor = await query` FOR user IN users @@ -1073,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: { @@ -1099,8 +1231,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1121,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.', }, }, }, @@ -1143,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: { @@ -1169,14 +1300,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -1200,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 = { @@ -1227,9 +1353,9 @@ describe('authenticate user account', () => { failedLoginAttempts: 0, } - await graphql( + await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -1253,14 +1379,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), collections: collectionNames, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -1280,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: { @@ -1313,14 +1437,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -1345,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: { @@ -1394,16 +1513,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1429,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([ @@ -1443,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: { @@ -1469,17 +1585,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1505,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([ @@ -1519,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: { @@ -1545,17 +1657,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1582,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([ @@ -1596,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: { @@ -1622,16 +1730,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1658,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([ @@ -1673,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: { @@ -1699,16 +1804,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1735,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([ @@ -1749,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: { @@ -1775,16 +1877,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1811,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([ @@ -1825,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: { @@ -1851,16 +1950,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1887,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([ @@ -1917,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: { @@ -1943,8 +2039,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1965,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.", }, }, }, @@ -1987,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: { @@ -2013,14 +2108,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -2044,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 = { @@ -2071,9 +2161,9 @@ describe('authenticate user account', () => { failedLoginAttempts: 0, } - await graphql( + await graphql({ schema, - ` + source: ` mutation { signIn( input: { @@ -2097,14 +2187,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), collections: collectionNames, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -2124,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: { @@ -2157,14 +2245,12 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, - transaction: jest - .fn() - .mockReturnValue({ step: jest.fn(), commit: jest.fn() }), + transaction: jest.fn().mockReturnValue({ step: jest.fn(), commit: jest.fn() }), uuidv4, auth: { bcrypt: { @@ -2189,7 +2275,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -2204,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: { @@ -2238,16 +2322,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2273,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([ @@ -2289,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: { @@ -2315,17 +2394,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2351,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([ @@ -2367,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: { @@ -2393,17 +2466,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2430,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([ @@ -2446,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: { @@ -2472,16 +2539,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2508,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([ @@ -2525,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: { @@ -2551,16 +2613,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2587,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([ @@ -2603,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: { @@ -2629,16 +2686,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2665,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([ @@ -2681,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: { @@ -2707,16 +2759,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2743,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/src/user/mutations/__tests__/sign-out.test.js b/api/src/user/mutations/__tests__/sign-out.test.js index e099130a3e..d2344384e5 100644 --- a/api/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 index 8d881b30e9..7dd4b0c961 100644 --- a/api/src/user/mutations/__tests__/sign-up.test.js +++ b/api/src/user/mutations/__tests__/sign-up.test.js @@ -1,7 +1,7 @@ -import { ensure, dbNameFromFile } from 'arango-tools' +import { dbNameFromFile } from 'arango-tools' +import { ensureDatabase as ensure } from '../../../testUtilities' 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' @@ -14,53 +14,12 @@ 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 } = process.env - -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] +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, - request + let query, drop, truncate, collections, transaction, schema, i18n, mockTokenize, mockNotify const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -76,10 +35,6 @@ describe('testing user sign up', () => { mutation: createMutationSchema(), }) mockTokenize = jest.fn().mockReturnValue('token') - request = { - protocol: 'https', - get: (text) => text, - } }) beforeEach(() => { mockNotify = jest.fn() @@ -127,12 +82,9 @@ describe('testing user 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -140,20 +92,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: ENGLISH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -163,14 +107,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, - response: mockedResponse, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -183,10 +126,10 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const cursor = await query` FOR user IN users @@ -194,44 +137,28 @@ describe('testing user sign up', () => { RETURN user ` const users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -239,20 +166,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: ENGLISH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -262,13 +181,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -281,10 +200,10 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -292,22 +211,16 @@ describe('testing user sign up', () => { 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -315,21 +228,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -339,13 +244,12 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, - response: mockedResponse, + request: { ip: '127.0.0.1' }, uuidv4, auth: { bcrypt, @@ -359,10 +263,10 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const cursor = await query` FOR user IN users @@ -370,44 +274,29 @@ describe('testing user sign up', () => { RETURN user ` const users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -415,20 +304,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: ENGLISH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -438,13 +319,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -457,10 +338,10 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -468,11 +349,8 @@ describe('testing user sign up', () => { i18n: {}, }).load('test.account@istio.actually.exists') - const verifyUrl = `https://${request.get('host')}/validate/token` - expect(mockNotify).toHaveBeenCalledWith({ user: user, - verifyUrl, }) }) }) @@ -514,12 +392,9 @@ describe('testing user sign up', () => { }) 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -527,21 +402,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -551,13 +418,12 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, - response: mockedResponse, + request: { ip: '127.0.0.1' }, uuidv4, auth: { bcrypt, @@ -573,55 +439,39 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + 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 users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('creates affiliation', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -629,21 +479,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -653,13 +495,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -674,10 +516,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const cursor = await query` FOR user IN users @@ -702,9 +544,9 @@ describe('testing user sign up', () => { expect(checkAffiliation).toMatchObject(expectedAffiliation) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -712,21 +554,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -736,13 +570,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -757,10 +591,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -768,22 +602,16 @@ describe('testing user sign up', () => { 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -791,22 +619,14 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -816,13 +636,12 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, - response: mockedResponse, + request: { ip: '127.0.0.1' }, uuidv4, auth: { bcrypt, @@ -838,55 +657,39 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + 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 users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('creates affiliation', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -894,21 +697,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -918,13 +713,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -939,10 +734,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const cursor = await query` FOR user IN users @@ -967,9 +762,9 @@ describe('testing user sign up', () => { expect(checkAffiliation).toMatchObject(expectedAffiliation) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -977,21 +772,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1001,13 +788,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1022,10 +809,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -1033,11 +820,8 @@ describe('testing user sign up', () => { i18n: {}, }).load('test.account@istio.actually.exists') - const verifyUrl = `https://${request.get('host')}/validate/token` - expect(mockNotify).toHaveBeenCalledWith({ user: user, - verifyUrl, }) }) }) @@ -1063,12 +847,9 @@ describe('testing user 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1076,20 +857,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1099,13 +872,12 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, - response: mockedResponse, + request: { ip: '127.0.0.1' }, uuidv4, auth: { bcrypt, @@ -1119,55 +891,38 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + 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 users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } - 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1175,20 +930,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: ENGLISH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1198,13 +945,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1217,10 +964,10 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -1228,22 +975,16 @@ describe('testing user sign up', () => { 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1251,21 +992,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1275,13 +1008,12 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, - response: mockedResponse, + request: { ip: '127.0.0.1' }, uuidv4, auth: { bcrypt, @@ -1295,55 +1027,39 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + 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 users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1351,20 +1067,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: ENGLISH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1374,13 +1082,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1393,10 +1101,10 @@ describe('testing user sign up', () => { loadUserByKey: loadUserByKey({ query }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -1404,11 +1112,8 @@ describe('testing user sign up', () => { i18n: {}, }).load('test.account@istio.actually.exists') - const verifyUrl = `https://${request.get('host')}/validate/token` - expect(mockNotify).toHaveBeenCalledWith({ user: user, - verifyUrl, }) }) }) @@ -1450,12 +1155,9 @@ describe('testing user sign up', () => { }) 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1463,21 +1165,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1487,14 +1181,14 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, - response: mockedResponse, + + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1509,55 +1203,39 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), }, notify: { - sendVerificationEmail: mockNotify, + 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 users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('creates affiliation', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1565,21 +1243,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1589,13 +1259,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1610,10 +1280,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const cursor = await query` FOR user IN users @@ -1638,9 +1308,9 @@ describe('testing user sign up', () => { expect(checkAffiliation).toMatchObject(expectedAffiliation) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1648,21 +1318,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1672,13 +1334,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1693,10 +1355,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -1704,22 +1366,16 @@ describe('testing user sign up', () => { 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( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1727,22 +1383,14 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1752,14 +1400,14 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, - response: mockedResponse, + + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1774,55 +1422,39 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), }, notify: { - sendVerificationEmail: mockNotify, + 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 users = await cursor.all() + expect(users).toHaveLength(1) 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, - }, + authenticateToken: 'token', + sendMethod: 'email', }, }, }, } 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.', + 'User: test.account@istio.actually.exists successfully created a new account, and sent auth msg.', ]) }) it('creates affiliation', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1830,21 +1462,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1854,13 +1478,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1875,10 +1499,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'en' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const cursor = await query` FOR user IN users @@ -1903,9 +1527,9 @@ describe('testing user sign up', () => { expect(checkAffiliation).toMatchObject(expectedAffiliation) }) it('sends verification email', async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -1913,21 +1537,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -1937,13 +1553,13 @@ describe('testing user sign up', () => { } } `, - null, - { - request, + rootValue: null, + contextValue: { query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -1958,10 +1574,10 @@ describe('testing user sign up', () => { loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const user = await loadUserByUserName({ query, @@ -1969,11 +1585,8 @@ describe('testing user sign up', () => { i18n: {}, }).load('test.account@istio.actually.exists') - const verifyUrl = `https://${request.get('host')}/validate/token` - expect(mockNotify).toHaveBeenCalledWith({ user: user, - verifyUrl, }) }) }) @@ -1999,9 +1612,9 @@ describe('testing user sign up', () => { }) describe('when the password is not strong enough', () => { it('returns a password too short error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2009,20 +1622,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "123" confirmPassword: "123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2032,13 +1637,14 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2055,10 +1661,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -2079,9 +1685,9 @@ describe('testing user sign up', () => { }) describe('when the passwords do not match', () => { it('returns a password not matching error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2089,20 +1695,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "321drowssaptset" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2112,13 +1710,14 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2135,10 +1734,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -2159,9 +1758,9 @@ describe('testing user sign up', () => { }) describe('when the user name (email) already in use', () => { it('returns an email already in use error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2169,20 +1768,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2192,13 +1783,14 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2211,7 +1803,6 @@ describe('testing user sign up', () => { load: jest.fn().mockReturnValue({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneValidated: false, emailValidated: false, }), @@ -2221,10 +1812,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -2256,9 +1847,9 @@ describe('testing user sign up', () => { }) }) it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2266,21 +1857,13 @@ describe('testing user sign up', () => { userName: "test@email.ca" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH signUpToken: "${token}" } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2290,8 +1873,8 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2299,8 +1882,10 @@ describe('testing user sign up', () => { step: jest.fn().mockReturnValue({ next: jest.fn(), }), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2345,18 +1930,17 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { signUp: { result: { code: 400, - description: - 'Unable to sign up, please contact org admin for a new invite.', + description: 'Unable to sign up, please contact org admin for a new invite.', }, }, }, @@ -2380,9 +1964,9 @@ describe('testing user sign up', () => { }) }) it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2390,21 +1974,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2414,8 +1990,8 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2423,8 +1999,10 @@ describe('testing user sign up', () => { step: jest.fn().mockReturnValue({ next: jest.fn(), }), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2445,18 +2023,17 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { signUp: { result: { code: 400, - description: - 'Unable to sign up, please contact org admin for a new invite.', + description: 'Unable to sign up, please contact org admin for a new invite.', }, }, }, @@ -2472,9 +2049,9 @@ describe('testing user sign up', () => { describe('given a cursor error', () => { describe('when gathering inserted user', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2482,20 +2059,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2505,8 +2074,8 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2514,8 +2083,10 @@ describe('testing user sign up', () => { step: jest.fn().mockReturnValue({ next: jest.fn().mockRejectedValue('Cursor Error'), }), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2560,14 +2131,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] + const error = [new GraphQLError('Unable to sign up. Please try again.')] expect(response.errors).toEqual(error) @@ -2580,9 +2149,9 @@ describe('testing user sign up', () => { describe('given a transaction error', () => { describe('when inserting user', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2590,20 +2159,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2613,15 +2174,17 @@ describe('testing user sign up', () => { } } `, - null, - { + 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, @@ -2666,14 +2229,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] + const error = [new GraphQLError('Unable to sign up. Please try again.')] expect(response.errors).toEqual(error) @@ -2694,9 +2255,9 @@ describe('testing user sign up', () => { }) }) it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2704,21 +2265,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2728,18 +2281,17 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ next: jest.fn() }) - .mockRejectedValue('Transaction Step Error'), + 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, @@ -2784,14 +2336,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] + const error = [new GraphQLError('Unable to sign up. Please try again.')] expect(response.errors).toEqual(error) @@ -2802,9 +2352,9 @@ describe('testing user sign up', () => { }) describe('when committing transaction', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2812,20 +2362,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2835,18 +2377,18 @@ describe('testing user sign up', () => { } } `, - null, - { + 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'), + commit: jest.fn().mockRejectedValue('Transaction Commit Error'), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2891,14 +2433,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError('Unable to sign up. Please try again.'), - ] + const error = [new GraphQLError('Unable to sign up. Please try again.')] expect(response.errors).toEqual(error) @@ -2926,9 +2466,9 @@ describe('testing user sign up', () => { }) describe('when the password is not strong enough', () => { it('returns a password too short error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -2936,20 +2476,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "123" confirmPassword: "123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -2959,13 +2491,14 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -2982,10 +2515,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -3006,9 +2539,9 @@ describe('testing user sign up', () => { }) describe('when the passwords do not match', () => { it('returns a password not matching error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3016,20 +2549,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "321drowssaptset" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3039,13 +2564,14 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -3062,10 +2588,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -3086,9 +2612,9 @@ describe('testing user sign up', () => { }) describe('when the user name (email) already in use', () => { it('returns an email already in use error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3096,20 +2622,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3119,13 +2637,14 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -3138,7 +2657,6 @@ describe('testing user sign up', () => { load: jest.fn().mockReturnValue({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', phoneValidated: false, emailValidated: false, }), @@ -3148,10 +2666,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -3183,9 +2701,9 @@ describe('testing user sign up', () => { }) }) it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3193,21 +2711,13 @@ describe('testing user sign up', () => { userName: "test@email.ca" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH signUpToken: "${token}" } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3217,8 +2727,8 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -3226,8 +2736,10 @@ describe('testing user sign up', () => { step: jest.fn().mockReturnValue({ next: jest.fn(), }), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -3272,10 +2784,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -3307,9 +2819,9 @@ describe('testing user sign up', () => { }) }) it('returns an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3317,21 +2829,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3341,8 +2845,8 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -3350,8 +2854,10 @@ describe('testing user sign up', () => { step: jest.fn().mockReturnValue({ next: jest.fn(), }), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -3372,10 +2878,10 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) const error = { data: { @@ -3399,9 +2905,9 @@ describe('testing user sign up', () => { describe('given a cursor error', () => { describe('when gathering inserted user', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3409,20 +2915,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3432,8 +2930,8 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -3441,8 +2939,10 @@ describe('testing user sign up', () => { step: jest.fn().mockReturnValue({ next: jest.fn().mockRejectedValue('Cursor Error'), }), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -3487,14 +2987,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] expect(response.errors).toEqual(error) @@ -3507,9 +3005,9 @@ describe('testing user sign up', () => { describe('given a transaction error', () => { describe('when inserting user', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3517,20 +3015,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3540,15 +3030,17 @@ describe('testing user sign up', () => { } } `, - null, - { + 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, @@ -3593,14 +3085,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] expect(response.errors).toEqual(error) @@ -3621,9 +3111,9 @@ describe('testing user sign up', () => { }) }) it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3631,21 +3121,13 @@ describe('testing user sign up', () => { 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 TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3655,18 +3137,17 @@ describe('testing user sign up', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce({ next: jest.fn() }) - .mockRejectedValue('Transaction Step Error'), + 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, @@ -3711,14 +3192,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] expect(response.errors).toEqual(error) @@ -3729,9 +3208,9 @@ describe('testing user sign up', () => { }) describe('when committing transaction', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -3739,20 +3218,12 @@ describe('testing user sign up', () => { userName: "test.account@istio.actually.exists" password: "testpassword123" confirmPassword: "testpassword123" - preferredLang: FRENCH } ) { result { - ... on AuthResult { - authToken - user { - id - userName - displayName - preferredLang - phoneValidated - emailValidated - } + ... on TFASignInResult { + authenticateToken + sendMethod } ... on SignUpError { code @@ -3762,18 +3233,18 @@ describe('testing user sign up', () => { } } `, - null, - { + 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'), + commit: jest.fn().mockRejectedValue('Transaction Commit Error'), + abort: jest.fn(), }), uuidv4, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize: mockTokenize, @@ -3818,14 +3289,12 @@ describe('testing user sign up', () => { }, }, notify: { - sendVerificationEmail: mockNotify, + sendAuthEmail: mockNotify, }, }, - ) + }) - const error = [ - new GraphQLError("Impossible de s'inscrire. Veuillez réessayer."), - ] + const error = [new GraphQLError("Impossible de s'inscrire. Veuillez réessayer.")] expect(response.errors).toEqual(error) diff --git a/api/src/user/mutations/__tests__/update-user-password.test.js b/api/src/user/mutations/__tests__/update-user-password.test.js index e5ac39aed4..b3910e594f 100644 --- a/api/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' @@ -13,42 +14,10 @@ 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 collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - const mockNotfiy = jest.fn() describe('authenticate user account', () => { @@ -88,9 +57,9 @@ describe('authenticate user account', () => { })) }) beforeEach(async () => { - await graphql( + await graphql({ schema, - ` + source: ` mutation { signUp( input: { @@ -98,21 +67,19 @@ 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: collectionNames, transaction, @@ -136,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" @@ -167,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: { @@ -190,13 +157,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -213,7 +181,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -226,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: { @@ -250,8 +216,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -271,7 +237,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotfiy, }, }, - ) + }) const expectedAuthResponse = { data: { @@ -284,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', () => { @@ -305,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: { @@ -328,13 +292,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, transaction, userKey: user._key, + request: { ip: '127.0.0.1' }, auth: { bcrypt, tokenize, @@ -351,7 +316,7 @@ describe('authenticate user account', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -364,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: { @@ -388,8 +351,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -409,7 +372,7 @@ describe('authenticate user account', () => { sendAuthEmail: mockNotfiy, }, }, - ) + }) const expectedAuthResponse = { data: { @@ -422,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.`]) }) }) }) @@ -446,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: { @@ -469,8 +430,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -489,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.', }, }, }, @@ -511,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: { @@ -534,8 +494,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -554,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.', }, }, }, @@ -576,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: { @@ -599,8 +558,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -619,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.', }, }, }, @@ -642,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: { @@ -665,15 +623,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -690,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([ @@ -706,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: { @@ -729,16 +684,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -755,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([ @@ -786,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: { @@ -809,8 +761,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -829,7 +781,7 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { @@ -851,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: { @@ -874,8 +826,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -894,7 +846,7 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { @@ -916,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: { @@ -939,8 +891,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -959,7 +911,7 @@ describe('authenticate user account', () => { cleanseInput, }, }, - ) + }) const error = { data: { @@ -982,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: { @@ -1005,15 +957,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1030,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([ @@ -1048,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: { @@ -1071,16 +1018,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -1097,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 index 41bc6222c4..ad8bfb23bf 100644 --- a/api/src/user/mutations/__tests__/update-user-profile.test.js +++ b/api/src/user/mutations/__tests__/update-user-profile.test.js @@ -1,6 +1,7 @@ import bcrypt from 'bcryptjs' import crypto from 'crypto' -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' @@ -12,41 +13,9 @@ 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 } = process.env - -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] +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 @@ -89,7 +58,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'french', tfaSendMethod: 'none', }) }) @@ -123,9 +91,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { displayName: "John Doe" }) { result { @@ -143,8 +111,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -167,7 +135,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -183,13 +151,11 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + 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 () => { + 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" @@ -197,12 +163,21 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + 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: "john.doe@istio.actually.works" } + input: { userName: "${newUsername}" } ) { result { ... on UpdateUserProfileResult { @@ -219,8 +194,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -228,6 +203,7 @@ describe('authenticate user account', () => { userKey: user._key, request: { get: jest.fn().mockReturnValue('domain.ca'), + ip: '127.0.0.1', }, auth: { bcrypt, @@ -244,176 +220,33 @@ describe('authenticate user account', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, - notify: { sendVerificationEmail: jest.fn() }, + notify: { sendVerificationEmail }, }, - ) + }) + // Does not change userName const expectedResponse = { data: { updateUserProfile: { result: { user: { - userName: 'john.doe@istio.actually.works', + userName: 'test.account@istio.actually.exists', }, 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: collectionNames, - 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, - }, + expect(sendVerificationEmail).toHaveBeenCalledWith({ + verifyUrl: verifyUrl, + userKey: user._key, + displayName: user.displayName, + userName: newUsername, }) - }) - 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: 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() - }) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) describe('user is not email validated', () => { it('does not change emailValidated value', async () => { @@ -427,9 +260,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - await graphql( + await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { userName: "john.doe@istio.actually.works" } @@ -449,8 +282,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -476,7 +309,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const checkCursor = await query` FOR user IN users @@ -488,80 +321,6 @@ describe('authenticate user account', () => { }) }) }) - 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: 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: { - 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', () => { @@ -586,7 +345,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', phoneValidated: true, tfaSendMethod: 'none', phoneDetails: updatedPhoneDetails, @@ -600,9 +358,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: PHONE }) { result { @@ -620,8 +378,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -644,7 +402,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -660,9 +418,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) describe('user is not phone validated', () => { @@ -687,7 +443,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', phoneValidated: false, tfaSendMethod: 'none', phoneDetails: updatedPhoneDetails, @@ -701,9 +456,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: PHONE }) { result { @@ -721,8 +476,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -745,7 +500,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -761,9 +516,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) }) @@ -774,7 +527,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: true, tfaSendMethod: 'none', }) @@ -787,9 +539,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: EMAIL }) { result { @@ -807,8 +559,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -831,7 +583,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -847,9 +599,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) describe('user is not email validated', () => { @@ -858,7 +608,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: false, tfaSendMethod: 'none', }) @@ -871,9 +620,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: EMAIL }) { result { @@ -891,8 +640,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -915,7 +664,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -931,9 +680,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) }) @@ -943,7 +690,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: true, tfaSendMethod: 'email', }) @@ -956,9 +702,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: NONE }) { result { @@ -976,8 +722,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1000,7 +746,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -1016,9 +762,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) }) @@ -1047,9 +791,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { displayName: "John Doe" }) { result { @@ -1067,8 +811,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1091,7 +835,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -1107,45 +851,52 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + 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 () => { + 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 - ` + FOR user IN users + FILTER user.userName == "test.account@istio.actually.exists" + RETURN user + ` const user = await cursor.next() - const response = await graphql( + 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, - ` - mutation { - updateUserProfile( - input: { userName: "john.doe@istio.actually.works" } - ) { - result { - ... on UpdateUserProfileResult { - status - user { - userName - } - } - ... on UpdateUserProfileError { - code - description + source: ` + mutation { + updateUserProfile( + input: { userName: "${newUsername}" } + ) { + result { + ... on UpdateUserProfileResult { + status + user { + userName } } + ... on UpdateUserProfileError { + code + description + } } } - `, - null, - { + } + `, + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1153,6 +904,7 @@ describe('authenticate user account', () => { userKey: user._key, request: { get: jest.fn().mockReturnValue('domain.ca'), + ip: '127.0.0.1', }, auth: { bcrypt, @@ -1169,177 +921,34 @@ describe('authenticate user account', () => { loadUserByUserName: loadUserByUserName({ query }), loadUserByKey: loadUserByKey({ query }), }, - notify: { sendVerificationEmail: jest.fn() }, + notify: { sendVerificationEmail }, }, - ) + }) const expectedResponse = { data: { updateUserProfile: { result: { user: { - userName: 'john.doe@istio.actually.works', + userName: 'test.account@istio.actually.exists', }, 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: collectionNames, - 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, - }, + expect(sendVerificationEmail).toHaveBeenCalledWith({ + verifyUrl: verifyUrl, + userKey: user._key, + displayName: user.displayName, + userName: newUsername, }) - }) - 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: 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() - }) + 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` @@ -1352,9 +961,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - await graphql( + await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { userName: "john.doe@istio.actually.works" } @@ -1374,8 +983,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1401,7 +1010,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const checkCursor = await query` FOR user IN users @@ -1414,80 +1023,6 @@ describe('authenticate user account', () => { }) }) }) - 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: 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: { - 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', () => { @@ -1512,7 +1047,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', phoneValidated: true, tfaSendMethod: 'none', phoneDetails: updatedPhoneDetails, @@ -1526,9 +1060,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: PHONE }) { result { @@ -1546,8 +1080,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1570,7 +1104,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -1586,9 +1120,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) describe('user is not phone validated', () => { @@ -1613,7 +1145,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', phoneValidated: false, tfaSendMethod: 'none', phoneDetails: updatedPhoneDetails, @@ -1627,9 +1158,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: PHONE }) { result { @@ -1647,8 +1178,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1671,7 +1202,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -1687,9 +1218,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) }) @@ -1700,7 +1229,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: true, tfaSendMethod: 'none', }) @@ -1713,9 +1241,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: EMAIL }) { result { @@ -1733,8 +1261,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1757,7 +1285,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -1773,9 +1301,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) describe('user is not email validated', () => { @@ -1784,7 +1310,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: false, tfaSendMethod: 'none', }) @@ -1797,9 +1322,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: EMAIL }) { result { @@ -1817,8 +1342,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1841,7 +1366,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -1857,9 +1382,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) }) @@ -1869,7 +1392,6 @@ describe('authenticate user account', () => { await collections.users.save({ displayName: 'Test Account', userName: 'test.account@istio.actually.exists', - preferredLang: 'english', emailValidated: true, tfaSendMethod: 'email', }) @@ -1882,9 +1404,9 @@ describe('authenticate user account', () => { ` const user = await cursor.next() - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile(input: { tfaSendMethod: NONE }) { result { @@ -1902,8 +1424,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -1926,7 +1448,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const expectedResponse = { data: { @@ -1942,9 +1464,7 @@ describe('authenticate user account', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated their profile.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated their profile.`]) }) }) }) @@ -1968,9 +1488,9 @@ describe('authenticate user account', () => { }) describe('user attempts to set email to one that is already in use', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { userName: "john.doe@istio.actually.works" } @@ -1990,8 +1510,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2017,7 +1537,7 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) const error = { data: { @@ -2039,15 +1559,14 @@ describe('authenticate user account', () => { describe('given a transaction step error', () => { describe('when updating profile', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { displayName: "John Smith" userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH } ) { result { @@ -2065,15 +1584,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2096,11 +1614,9 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) - const error = [ - new GraphQLError('Unable to update profile. Please try again.'), - ] + const error = [new GraphQLError('Unable to update profile. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2112,15 +1628,14 @@ describe('authenticate user account', () => { describe('given a transaction step error', () => { describe('when updating profile', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { displayName: "John Smith" userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH } ) { result { @@ -2138,16 +1653,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2170,11 +1684,9 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) - const error = [ - new GraphQLError('Unable to update profile. Please try again.'), - ] + const error = [new GraphQLError('Unable to update profile. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2201,9 +1713,9 @@ describe('authenticate user account', () => { }) describe('user attempts to set email to one that is already in use', () => { it('returns an error message', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { userName: "john.doe@istio.actually.works" } @@ -2223,8 +1735,8 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, collections: collectionNames, @@ -2250,15 +1762,14 @@ describe('authenticate user account', () => { }, 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.", + description: "Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.", }, }, }, @@ -2273,15 +1784,14 @@ describe('authenticate user account', () => { describe('given a transaction step error', () => { describe('when updating profile', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { displayName: "John Smith" userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH } ) { result { @@ -2299,15 +1809,14 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2330,13 +1839,9 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le profil. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le profil. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2348,15 +1853,14 @@ describe('authenticate user account', () => { describe('given a transaction step error', () => { describe('when updating profile', () => { it('throws an error', async () => { - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { updateUserProfile( input: { displayName: "John Smith" userName: "john.smith@istio.actually.works" - preferredLang: ENGLISH } ) { result { @@ -2374,16 +1878,15 @@ describe('authenticate user account', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query, 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: { @@ -2406,13 +1909,9 @@ describe('authenticate user account', () => { }, notify: { sendVerificationEmail: jest.fn() }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le profil. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le profil. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/user/mutations/__tests__/verify-account.test.js b/api/src/user/mutations/__tests__/verify-account.test.js index 68182ffa94..ec71acb6cd 100644 --- a/api/src/user/mutations/__tests__/verify-account.test.js +++ b/api/src/user/mutations/__tests__/verify-account.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' @@ -8,44 +9,12 @@ import { createQuerySchema } from '../../../query' import { createMutationSchema } from '../../../mutation' import { cleanseInput } from '../../../validators' import { tokenize, verifyToken } from '../../../auth' -import { loadUserByKey } from '../../loaders' +import { loadUserByKey, loadUserByUserName } from '../../loaders' import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' const { DB_PASS: rootPass, DB_URL: url } = process.env -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('user send password reset email', () => { let query, drop, truncate, collections, transaction, schema, request, i18n const consoleOutput = [] @@ -53,7 +22,7 @@ describe('user send password reset email', () => { const mockedWarn = (output) => consoleOutput.push(output) const mockedError = (output) => consoleOutput.push(output) - beforeAll(() => { + beforeAll(async () => { console.info = mockedInfo console.warn = mockedWarn console.error = mockedError @@ -65,31 +34,27 @@ describe('user send password reset email', () => { 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(() => { + afterEach(async () => { consoleOutput.length = 0 + await truncate() + }) + afterAll(async () => { + await drop() }) describe('given a successful validation', () => { - 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({ @@ -109,94 +74,28 @@ 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 a successful status message', async () => { + 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 == "test.account@istio.actually.exists" + FILTER user.userName == ${previousUserName} RETURN user ` let user = await cursor.next() - const token = tokenize({ parameters: { userKey: user._key } }) + const newUserName = 'john.doe@istio.actually.works' - 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: collectionNames, - 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, userName: newUserName } }) - const token = tokenize({ parameters: { userKey: user._key } }) + const sendUpdatedUserNameEmail = jest.fn() - await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyAccount(input: { verifyTokenString: "${token}" }) { result { @@ -211,8 +110,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: user._key, @@ -227,170 +126,40 @@ describe('user send password reset email', () => { }, loaders: { loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ 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, + notify: { sendUpdatedUserNameEmail }, }, }) - }) - 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: collectionNames, - 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.", + status: 'Successfully email verified account.', }, }, }, } - 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: collectionNames, - transaction, - auth: { - verifyToken: verifyToken({}), - }, - validators: { - cleanseInput, - }, - loaders: { - loadUserByKey: loadUserByKey({ query }), - }, - }, - ) + expect(sendUpdatedUserNameEmail).toHaveBeenCalledWith({ + previousUserName: previousUserName, + newUserName: newUserName, + displayName: user.displayName, + userKey: user._key, + }) cursor = await query` FOR user IN users - FILTER user.userName == "test.account@istio.actually.exists" + 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.`]) }) }) }) @@ -416,78 +185,9 @@ describe('user send password reset email', () => { 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: collectionNames, - 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( + const response = await graphql({ schema, - ` + source: ` mutation { verifyAccount(input: { verifyTokenString: "${token}" }) { result { @@ -502,8 +202,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, @@ -521,310 +221,22 @@ describe('user send password reset email', () => { load: jest.fn().mockReturnValue({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'english', 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 }, }) - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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: collectionNames, - 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.', + description: 'Unable to verify account. Please request a new email.', }, }, }, @@ -842,9 +254,9 @@ describe('user send password reset email', () => { parameters: { userKey: undefined }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyAccount(input: { verifyTokenString: "${token}" }) { result { @@ -859,8 +271,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, @@ -878,22 +290,22 @@ describe('user send password reset email', () => { load: jest.fn().mockReturnValue({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'english', tfaValidated: false, emailValidated: false, }), }, + loadUserByUserName: loadUserByUserName({ query }), }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, }, - ) + }) const error = { data: { verifyAccount: { result: { code: 400, - description: - 'Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.', + description: 'Unable to verify account. Please request a new email.', }, }, }, @@ -908,11 +320,11 @@ describe('user send password reset email', () => { describe('user cannot be found in db', () => { it('returns an error message', async () => { const token = tokenize({ - parameters: { userKey: 1 }, + parameters: { userKey: 1, userName: 'john.doe@istio.actually.exists' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyAccount(input: { verifyTokenString: "${token}" }) { result { @@ -927,8 +339,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 1, @@ -945,17 +357,18 @@ describe('user send password reset email', () => { loadUserByKey: { load: jest.fn().mockReturnValue(undefined), }, + loadUserByUserName: loadUserByUserName({ query }), }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, }, - ) + }) const error = { data: { verifyAccount: { result: { code: 400, - description: - 'Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.', + description: 'Unable to verify account. Please request a new email.', }, }, }, @@ -971,12 +384,12 @@ describe('user send password reset email', () => { describe('when upserting validation', () => { it('throws an error', async () => { const token = tokenize({ - parameters: { userKey: 123 }, + parameters: { userKey: 123, userName: 'john.doe@istio.actually.exists' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyAccount(input: { verifyTokenString: "${token}" }) { result { @@ -991,19 +404,16 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, query, collections: collectionNames, transaction: 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(), }), auth: { verifyToken: verifyToken({}), @@ -1017,20 +427,17 @@ describe('user send password reset email', () => { _key: 123, userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'english', tfaValidated: false, emailValidated: false, }), }, + loadUserByUserName: loadUserByUserName({ query, userKey: 123, i18n }), }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de vérifier le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Unable to verify account. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1043,12 +450,12 @@ describe('user send password reset email', () => { describe('when upserting validation', () => { it('throws an error', async () => { const token = tokenize({ - parameters: { userKey: 123 }, + parameters: { userKey: 123, userName: 'john.doe@istio.actually.exists' }, }) - const response = await graphql( + const response = await graphql({ schema, - ` + source: ` mutation { verifyAccount(input: { verifyTokenString: "${token}" }) { result { @@ -1063,8 +470,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, request, userKey: 123, @@ -1072,11 +479,8 @@ describe('user send password reset email', () => { collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue({}), - commit: jest - .fn() - .mockRejectedValue( - new Error('Transaction commit error occurred.'), - ), + commit: jest.fn().mockRejectedValue(new Error('Transaction commit error occurred.')), + abort: jest.fn(), }), auth: { verifyToken: verifyToken({}), @@ -1090,20 +494,17 @@ describe('user send password reset email', () => { _key: 123, userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'english', tfaValidated: false, emailValidated: false, }), }, + loadUserByUserName: loadUserByUserName({ query, userKey: 123, i18n }), }, + notify: { sendUpdatedUserNameEmail: jest.fn() }, }, - ) + }) - const error = [ - new GraphQLError( - 'Impossible de vérifier le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Unable to verify account. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/user/mutations/__tests__/verify-phone-number.test.js b/api/src/user/mutations/__tests__/verify-phone-number.test.js index 5ebd181065..4da0f61bfc 100644 --- a/api/src/user/mutations/__tests__/verify-phone-number.test.js +++ b/api/src/user/mutations/__tests__/verify-phone-number.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' @@ -9,42 +10,10 @@ 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 -const collectionNames = [ - 'users', - 'organizations', - 'domains', - 'dkim', - 'dkimResults', - 'dmarc', - 'spf', - 'https', - 'ssl', - 'dkimGuidanceTags', - 'dmarcGuidanceTags', - 'spfGuidanceTags', - 'httpsGuidanceTags', - 'sslGuidanceTags', - 'chartSummaries', - 'dmarcSummaries', - 'aggregateGuidanceTags', - 'scanSummaryCriteria', - 'chartSummaryCriteria', - 'scanSummaries', - 'affiliations', - 'claims', - 'domainsDKIM', - 'dkimToDkimResults', - 'domainsDMARC', - 'domainsSPF', - 'domainsHTTPS', - 'domainsSSL', - 'ownership', - 'domainsToDmarcSummaries', -] - describe('user send password reset email', () => { let query, drop, truncate, collections, transaction, schema, i18n, user const consoleOutput = [] @@ -83,7 +52,6 @@ describe('user send password reset email', () => { user = await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, tfaCode: 123456, @@ -111,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 { @@ -131,8 +99,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, @@ -148,7 +116,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -157,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 { @@ -190,8 +155,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, @@ -207,7 +172,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const cursor = await query` FOR user IN users @@ -217,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', () => { @@ -238,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 { @@ -258,8 +221,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, @@ -275,7 +238,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResult = { data: { @@ -292,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 { @@ -317,8 +278,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query, @@ -334,7 +295,7 @@ describe('user send password reset email', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const cursor = await query` FOR user IN users @@ -344,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.`]) }) }) }) @@ -368,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 { @@ -388,8 +347,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -406,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.', }, }, }, @@ -428,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 { @@ -448,8 +406,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -467,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.', }, }, }, @@ -490,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 { @@ -510,16 +467,15 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, 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({ @@ -533,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([ @@ -551,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 { @@ -571,17 +523,16 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, 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({ @@ -595,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([ @@ -628,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 { @@ -648,8 +595,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -666,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.', }, }, }, @@ -688,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 { @@ -708,8 +654,8 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, @@ -727,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.', }, }, }, @@ -750,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 { @@ -770,16 +715,15 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, 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({ @@ -793,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([ @@ -811,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 { @@ -831,17 +771,16 @@ describe('user send password reset email', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query, 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({ @@ -855,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 index b8806f15d5..1761186ffa 100644 --- a/api/src/user/mutations/authenticate.js +++ b/api/src/user/mutations/authenticate.js @@ -2,28 +2,33 @@ 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 { SIGN_IN_KEY, REFRESH_KEY, REFRESH_TOKEN_EXPIRY } = process.env +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: GraphQLNonNull(GraphQLInt), + type: new GraphQLNonNull(GraphQLInt), description: 'Security code found in text msg, or email inbox.', }, authenticateToken: { - type: GraphQLNonNull(GraphQLString), + 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.', + description: 'Authenticate union returning either a `authResult` or `authenticateError` object.', resolve: (payload) => payload, }, }), @@ -36,6 +41,7 @@ export const authenticate = new mutationWithClientMutationId({ collections, transaction, uuidv4, + jwt, auth: { tokenize, verifyToken }, loaders: { loadUserByKey }, validators: { cleanseInput }, @@ -44,6 +50,7 @@ export const authenticate = new mutationWithClientMutationId({ // Cleanse Inputs const authenticationCode = args.authenticationCode const authenticationToken = cleanseInput(args.authenticateToken) + const sendMethod = cleanseInput(args.sendMethod) // Gather token parameters const tokenParameters = verifyToken({ @@ -51,10 +58,7 @@ export const authenticate = new mutationWithClientMutationId({ secret: String(SIGN_IN_KEY), }) - if ( - tokenParameters.userKey === 'undefined' || - typeof tokenParameters.userKey === 'undefined' - ) { + if (tokenParameters.userKey === 'undefined' || typeof tokenParameters.userKey === 'undefined') { console.warn(`Authentication token does not contain the userKey`) return { _type: 'error', @@ -68,9 +72,7 @@ export const authenticate = new mutationWithClientMutationId({ // Replace with userRequired() if (typeof user === 'undefined') { - console.warn( - `User: ${tokenParameters.userKey} attempted to authenticate, no account is associated with this id.`, - ) + console.warn(`User: ${tokenParameters.userKey} attempted to authenticate, no account is associated with this id.`) return { _type: 'error', code: 400, @@ -81,13 +83,12 @@ export const authenticate = new mutationWithClientMutationId({ // 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() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), + expiresAt: new Date(new Date().getTime() + ms(REFRESH_TOKEN_EXPIRY)), } // Setup Transaction @@ -101,11 +102,13 @@ export const authenticate = new mutationWithClientMutationId({ UPSERT { _key: ${user._key} } INSERT { tfaCode: null, - refreshInfo: ${refreshInfo} + refreshInfo: ${refreshInfo}, + lastLogin: ${loginDate} } UPDATE { tfaCode: null, - refreshInfo: ${refreshInfo} + refreshInfo: ${refreshInfo}, + lastLogin: ${loginDate} } IN users `, @@ -114,22 +117,53 @@ export const authenticate = new mutationWithClientMutationId({ 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}`, - ) + 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({ parameters: { userKey: user._key } }) + 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 }, - expPeriod: 168, secret: String(REFRESH_KEY), }) @@ -143,8 +177,9 @@ export const authenticate = new mutationWithClientMutationId({ // 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: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, + maxAge: tokenMaxAgeSeconds * 1000, httpOnly: true, secure: true, sameSite: true, @@ -153,9 +188,7 @@ export const authenticate = new mutationWithClientMutationId({ response.cookie('refresh_token', refreshToken, cookieData) - console.info( - `User: ${user._key} successfully authenticated their account.`, - ) + console.info(`User: ${user._key} successfully authenticated their account.`) return { _type: 'authResult', @@ -163,9 +196,38 @@ export const authenticate = new mutationWithClientMutationId({ user, } } else { - console.warn( - `User: ${user._key} attempted to authenticate their account, however the tfaCodes did not match.`, - ) + 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 index b8488a6542..7a03baa586 100644 --- a/api/src/user/mutations/close-account.js +++ b/api/src/user/mutations/close-account.js @@ -1,37 +1,23 @@ 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 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.', - }, - }), +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.', + 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 }, - }, + { i18n, query, collections, transaction, request: { ip }, auth: { userRequired }, validators: { cleanseInput } }, ) => { let submittedUserId if (args?.userId) { @@ -40,418 +26,145 @@ export const closeAccount = new mutationWithClientMutationId({ 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 userId = user._id + const targetUserName = user.userName - 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 - } + // Setup Trans action + const trx = await transaction(collections) - // 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 - ` + 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( - `Database error occurred when getting affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, + `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.`)) } - let orgOwnerAffiliationCheck try { - orgOwnerAffiliationCheck = await orgOwnerAffiliationCursor.all() + await trx.step( + () => query` + WITH users + REMOVE PARSE_IDENTIFIER(${userId}).key + IN users OPTIONS { waitForSync: true } + `, + ) } catch (err) { console.error( - `Cursor error occurred when getting affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, + `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.`)) } - // Setup Trans action - const trx = await transaction(collections) - - // 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.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.`)) + } - 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.`)) - } - } + 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 + }, + }) - 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.`)) - } + return { + _type: 'regular', + status: i18n._(t`Successfully closed account.`), + } + }, +}) - 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.`)) - } +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 + } - 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.`), - ) - } + const user = await userRequired() - 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.`), - ) - } + let userId = '' + let targetUserName = '' - 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.`), - ) - } - } + 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.`), } + } - // 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.`)) + 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( @@ -466,6 +179,7 @@ export const closeAccount = new mutationWithClientMutationId({ 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.`)) } @@ -481,25 +195,40 @@ export const closeAccount = new mutationWithClientMutationId({ 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}`, - ) + 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.`, - ) + 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 index 372334dd70..0a11011ec8 100644 --- a/api/src/user/mutations/index.js +++ b/api/src/user/mutations/index.js @@ -1,9 +1,10 @@ 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-email-verification' export * from './send-password-reset' export * from './set-phone-number' export * from './sign-in' diff --git a/api/src/user/mutations/refresh-tokens.js b/api/src/user/mutations/refresh-tokens.js index 6d87f90ca3..0028b753e5 100644 --- a/api/src/user/mutations/refresh-tokens.js +++ b/api/src/user/mutations/refresh-tokens.js @@ -2,8 +2,9 @@ import { t } from '@lingui/macro' import { mutationWithClientMutationId } from 'graphql-relay' import { refreshTokensUnion } from '../unions' +import ms from 'ms' -const { REFRESH_TOKEN_EXPIRY, REFRESH_KEY } = process.env +const { REFRESH_TOKEN_EXPIRY, REFRESH_KEY, AUTHENTICATED_KEY, AUTH_TOKEN_EXPIRY } = process.env export const refreshTokens = new mutationWithClientMutationId({ name: 'RefreshTokens', @@ -12,8 +13,7 @@ export const refreshTokens = new mutationWithClientMutationId({ outputFields: () => ({ result: { type: refreshTokensUnion, - description: - 'Refresh tokens union returning either a `authResult` or `authenticateError` object.', + description: 'Refresh tokens union returning either a `authResult` or `authenticateError` object.', resolve: (payload) => payload, }, }), @@ -40,9 +40,7 @@ export const refreshTokens = new mutationWithClientMutationId({ } if (typeof refreshToken === 'undefined') { - console.warn( - `User attempted to refresh tokens without refresh_token set.`, - ) + console.warn(`User attempted to refresh tokens without refresh_token set.`) return { _type: 'error', code: 400, @@ -54,9 +52,7 @@ export const refreshTokens = new mutationWithClientMutationId({ try { decodedRefreshToken = jwt.verify(refreshToken, REFRESH_KEY) } catch (err) { - console.warn( - `User attempted to verify refresh token, however the token is invalid: ${err}`, - ) + console.warn(`User attempted to verify refresh token, however the token is invalid: ${err}`) return { _type: 'error', code: 400, @@ -69,9 +65,7 @@ export const refreshTokens = new mutationWithClientMutationId({ const user = await loadUserByKey.load(userKey) if (typeof user === 'undefined') { - console.warn( - `User: ${userKey} attempted to refresh tokens with an invalid user id.`, - ) + console.warn(`User: ${userKey} attempted to refresh tokens with an invalid user id.`) return { _type: 'error', code: 400, @@ -84,9 +78,7 @@ export const refreshTokens = new mutationWithClientMutationId({ const expiryTime = moment(user.refreshInfo.expiresAt).format() if (moment(currentTime).isAfter(expiryTime)) { - console.warn( - `User: ${userKey} attempted to refresh tokens with an expired uuid.`, - ) + console.warn(`User: ${userKey} attempted to refresh tokens with an expired uuid.`) return { _type: 'error', code: 400, @@ -96,9 +88,7 @@ export const refreshTokens = new mutationWithClientMutationId({ // check to see if token ids match if (user.refreshInfo.refreshId !== uuid) { - console.warn( - `User: ${userKey} attempted to refresh tokens with non matching uuids.`, - ) + console.warn(`User: ${userKey} attempted to refresh tokens with non matching uuids.`) return { _type: 'error', code: 400, @@ -111,9 +101,7 @@ export const refreshTokens = new mutationWithClientMutationId({ const refreshInfo = { refreshId: newRefreshId, rememberMe: user.refreshInfo.rememberMe, - expiresAt: new Date( - new Date().getTime() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), + expiresAt: new Date(new Date().getTime() + ms(String(REFRESH_TOKEN_EXPIRY))), } // Setup Transaction @@ -130,28 +118,30 @@ export const refreshTokens = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Trx step error occurred when attempting to refresh tokens for user: ${userKey}: ${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}`, - ) + 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({ parameters: { userKey } }) + 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 }, - expPeriod: 168, secret: String(REFRESH_KEY), }) @@ -165,8 +155,9 @@ export const refreshTokens = new mutationWithClientMutationId({ // 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: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, + maxAge: tokenMaxAgeSeconds * 1000, httpOnly: true, secure: true, sameSite: true, diff --git a/api/src/user/mutations/remove-phone-number.js b/api/src/user/mutations/remove-phone-number.js index 679a0f3916..b1d449b1a0 100644 --- a/api/src/user/mutations/remove-phone-number.js +++ b/api/src/user/mutations/remove-phone-number.js @@ -5,8 +5,7 @@ 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.', + description: 'This mutation allows for users to remove a phone number from their account.', outputFields: () => ({ result: { type: removePhoneNumberUnion, @@ -15,10 +14,7 @@ export const removePhoneNumber = new mutationWithClientMutationId({ resolve: (payload) => payload, }, }), - mutateAndGetPayload: async ( - _args, - { i18n, collections, query, transaction, auth: { userRequired } }, - ) => { + mutateAndGetPayload: async (_args, { i18n, collections, query, transaction, auth: { userRequired } }) => { // Get requesting user const user = await userRequired() @@ -36,7 +32,7 @@ export const removePhoneNumber = new mutationWithClientMutationId({ () => query` WITH users UPSERT { _key: ${user._key} } - INSERT { + INSERT { phoneDetails: null, phoneValidated: false, tfaSendMethod: ${tfaSendMethod} @@ -50,23 +46,17 @@ export const removePhoneNumber = new mutationWithClientMutationId({ `, ) } 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.`), - ) + 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}`, - ) - throw new Error( - i18n._(t`Unable to remove phone number. Please try again.`), - ) + 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.`) diff --git a/api/src/user/mutations/reset-password.js b/api/src/user/mutations/reset-password.js index 2dad6285ea..8deb0bbb7b 100644 --- a/api/src/user/mutations/reset-password.js +++ b/api/src/user/mutations/reset-password.js @@ -4,30 +4,29 @@ 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.', + description: 'This mutation allows the user to take the token they received in their email to reset their password.', inputFields: () => ({ password: { - type: GraphQLNonNull(GraphQLString), + type: new GraphQLNonNull(GraphQLString), description: 'The users new password.', }, confirmPassword: { - type: GraphQLNonNull(GraphQLString), + type: new 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.', + 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.', + description: '`ResetPasswordUnion` returning either a `ResetPasswordResult`, or `ResetPasswordError` object.', resolve: (payload) => payload, }, }), @@ -49,22 +48,17 @@ export const resetPassword = new mutationWithClientMutationId({ const resetToken = cleanseInput(args.resetToken) // Check if reset token is valid - const tokenParameters = verifyToken({ token: resetToken }) + 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' - ) { + 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.`, - ), + description: i18n._(t`Incorrect token value. Please request a new email.`), } } @@ -83,20 +77,6 @@ export const resetPassword = new mutationWithClientMutationId({ } } - // 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( @@ -140,18 +120,16 @@ export const resetPassword = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Trx step error occurred when user: ${user._key} attempted to reset their password: ${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}`, - ) + 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.`)) } diff --git a/api/src/user/mutations/send-email-verification.js b/api/src/user/mutations/send-email-verification.js deleted file mode 100644 index db60c2b5f4..0000000000 --- a/api/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/src/user/mutations/send-password-reset.js b/api/src/user/mutations/send-password-reset.js index b55f342e9a..5578b94147 100644 --- a/api/src/user/mutations/send-password-reset.js +++ b/api/src/user/mutations/send-password-reset.js @@ -3,22 +3,22 @@ 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: GraphQLNonNull(GraphQLEmailAddress), - description: - 'User name for the account you would like to receive a password reset link for.', + 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.', + description: 'Informs the user if the password reset email was sent successfully.', resolve: async (payload) => { return payload.status }, @@ -42,15 +42,15 @@ export const sendPasswordResetLink = new mutationWithClientMutationId({ 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.`, - ) + 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.`, @@ -58,9 +58,7 @@ export const sendPasswordResetLink = new mutationWithClientMutationId({ } return { - status: i18n._( - t`If an account with this username is found, a password reset link will be found in your inbox.`, - ), + 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 index d903098388..09f874d6e7 100644 --- a/api/src/user/mutations/set-phone-number.js +++ b/api/src/user/mutations/set-phone-number.js @@ -14,15 +14,14 @@ export const setPhoneNumber = new mutationWithClientMutationId({ '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), + 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.', + description: '`SetPhoneNumberUnion` returning either a `SetPhoneNumberResult`, or `SetPhoneNumberError` object.', resolve: (payload) => payload, }, }), @@ -36,7 +35,7 @@ export const setPhoneNumber = new mutationWithClientMutationId({ auth: { userRequired }, loaders: { loadUserByKey }, validators: { cleanseInput }, - notify: { sendTfaTextMsg }, + notify: { sendAuthTextMsg }, }, ) => { // Cleanse input @@ -53,12 +52,9 @@ export const setPhoneNumber = new mutationWithClientMutationId({ iv: crypto.randomBytes(12).toString('hex'), } - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(phoneDetails.iv, 'hex'), - { authTagLength: 16 }, - ) + 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') @@ -96,18 +92,16 @@ export const setPhoneNumber = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Trx step error occurred for user: ${user._key} when upserting phone number information: ${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}`, - ) + 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.`)) } @@ -115,15 +109,13 @@ export const setPhoneNumber = new mutationWithClientMutationId({ await loadUserByKey.clear(user._key) user = await loadUserByKey.load(user._key) - await sendTfaTextMsg({ phoneNumber, user }) + 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.`, - ), + 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 index 9cbbd11314..bf0efdcdda 100644 --- a/api/src/user/mutations/sign-in.js +++ b/api/src/user/mutations/sign-in.js @@ -4,8 +4,9 @@ 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 } = process.env +const { SIGN_IN_KEY, REFRESH_TOKEN_EXPIRY, REFRESH_KEY, AUTHENTICATED_KEY, AUTH_TOKEN_EXPIRY } = process.env export const signIn = new mutationWithClientMutationId({ name: 'SignIn', @@ -13,18 +14,17 @@ export const signIn = new mutationWithClientMutationId({ '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), + type: new GraphQLNonNull(GraphQLEmailAddress), description: 'The email the user signed up with.', }, password: { - type: GraphQLNonNull(GraphQLString), + 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.', + description: 'Whether or not the user wants to stay signed in after leaving the site.', }, }), outputFields: () => ({ @@ -44,6 +44,7 @@ export const signIn = new mutationWithClientMutationId({ transaction, uuidv4, response, + jwt, auth: { tokenize, bcrypt }, loaders: { loadUserByUserName }, validators: { cleanseInput }, @@ -60,29 +61,21 @@ export const signIn = new mutationWithClientMutationId({ // Replace with userRequired() if (typeof user === 'undefined') { - console.warn( - `User: ${userName} attempted to sign in, no account is associated with this email.`, - ) + 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.`, - ), + 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.`, - ) + 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.`, - ), + description: i18n._(t`Too many failed login attempts, please reset your password, and try again.`), } } else { // Setup Transaction @@ -100,18 +93,15 @@ export const signIn = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Trx step error occurred when resetting failed login attempts for user: ${user._key}: ${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() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), + expiresAt: new Date(new Date().getTime() + ms(String(REFRESH_TOKEN_EXPIRY))), rememberMe, } @@ -137,18 +127,16 @@ export const signIn = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Trx step error occurred when inserting TFA code for user: ${user._key}: ${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}`, - ) + 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.`)) } @@ -156,23 +144,33 @@ export const signIn = new mutationWithClientMutationId({ 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 === 'phone') { - await sendAuthTextMsg({ user }) - sendMethod = 'text' - } else { + 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.`, - ) + 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), + secret: String(SIGN_IN_KEY), // SIGN_IN_KEY is reserved for signing TFA tokens }) return { @@ -181,13 +179,14 @@ export const signIn = new mutationWithClientMutationId({ authenticateToken, } } else { + const loginDate = new Date().toISOString() try { await trx.step( () => query` WITH users UPSERT { _key: ${user._key} } - INSERT { refreshInfo: ${refreshInfo} } - UPDATE { refreshInfo: ${refreshInfo} } + INSERT { refreshInfo: ${refreshInfo}, lastLogin: ${loginDate} } + UPDATE { refreshInfo: ${refreshInfo}, lastLogin: ${loginDate} } IN users `, ) @@ -195,25 +194,27 @@ export const signIn = new mutationWithClientMutationId({ 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}`, - ) + 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 }, - expPeriod: 168, secret: String(REFRESH_KEY), }) @@ -227,8 +228,9 @@ export const signIn = new mutationWithClientMutationId({ // 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: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, + maxAge: tokenMaxAgeSeconds * 1000, httpOnly: true, secure: true, sameSite: true, @@ -237,9 +239,7 @@ export const signIn = new mutationWithClientMutationId({ response.cookie('refresh_token', refreshToken, cookieData) - console.info( - `User: ${user._key} successfully signed in, and sent auth msg.`, - ) + console.info(`User: ${user._key} successfully signed in, and sent auth msg.`) return { _type: 'regular', @@ -266,27 +266,23 @@ export const signIn = new mutationWithClientMutationId({ 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}`, - ) + 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.`, - ) + 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.`, - ), + 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 index 965ccb2aaf..29e5b032d2 100644 --- a/api/src/user/mutations/sign-out.js +++ b/api/src/user/mutations/sign-out.js @@ -1,6 +1,6 @@ -import { t } from '@lingui/macro' -import { GraphQLString } from 'graphql' -import { mutationWithClientMutationId } from 'graphql-relay' +import {t} from '@lingui/macro' +import {GraphQLString} from 'graphql' +import {mutationWithClientMutationId} from 'graphql-relay' export const signOut = new mutationWithClientMutationId({ name: 'SignOut', @@ -10,10 +10,10 @@ export const signOut = new mutationWithClientMutationId({ status: { type: GraphQLString, description: 'Status of the users signing-out.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, }), - mutateAndGetPayload: async (_, { i18n, response }) => { + mutateAndGetPayload: async (_, {i18n, response}) => { response.cookie('refresh_token', '', { httpOnly: true, expires: new Date(0), diff --git a/api/src/user/mutations/sign-up.js b/api/src/user/mutations/sign-up.js index 0ff28db6ae..16dfb0eed7 100644 --- a/api/src/user/mutations/sign-up.js +++ b/api/src/user/mutations/sign-up.js @@ -3,54 +3,47 @@ import { mutationWithClientMutationId } from 'graphql-relay' import { t } from '@lingui/macro' import { GraphQLEmailAddress } from 'graphql-scalars' -import { LanguageEnums } from '../../enums' 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, REFRESH_KEY } = process.env +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.', + description: 'This mutation allows for new users to sign up for our sites services.', inputFields: () => ({ displayName: { - type: GraphQLNonNull(GraphQLString), + type: new GraphQLNonNull(GraphQLString), description: 'The name that will be displayed to other users.', }, userName: { - type: GraphQLNonNull(GraphQLEmailAddress), + type: new GraphQLNonNull(GraphQLEmailAddress), description: 'Email address that the user will use to authenticate with.', }, password: { - type: GraphQLNonNull(GraphQLString), + type: new 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.', + 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.', + 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.', + 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.', + description: '`SignUpUnion` returning either a `TFASignInResult`, or `SignUpError` object.', resolve: (payload) => payload, }, }), @@ -61,12 +54,11 @@ export const signUp = new mutationWithClientMutationId({ collections, query, transaction, - request, - response, uuidv4, + request: { ip }, auth: { bcrypt, tokenize, verifyToken }, loaders: { loadOrgByKey, loadUserByUserName, loadUserByKey }, - notify: { sendVerificationEmail }, + notify: { sendAuthEmail }, validators: { cleanseInput }, }, ) => { @@ -75,15 +67,22 @@ export const signUp = new mutationWithClientMutationId({ 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 + 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.`, - ) + console.warn(`User: ${userName} tried to sign up but did not meet requirements.`) return { _type: 'error', code: 400, @@ -93,9 +92,7 @@ export const signUp = new mutationWithClientMutationId({ // Check that password and password confirmation match if (password !== confirmPassword) { - console.warn( - `User: ${userName} tried to sign up but passwords do not match.`, - ) + console.warn(`User: ${userName} tried to sign up but passwords do not match.`) return { _type: 'error', code: 400, @@ -107,9 +104,7 @@ export const signUp = new mutationWithClientMutationId({ 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.`, - ) + console.warn(`User: ${userName} tried to sign up, however there is already an account in use with that email.`) return { _type: 'error', code: 400, @@ -121,23 +116,29 @@ export const signUp = new mutationWithClientMutationId({ 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, - preferredLang: preferredLang, phoneValidated: false, emailValidated: false, + insideUser: false, + emailUpdateOptions, failedLoginAttempts: 0, - tfaSendMethod: 'none', + tfaSendMethod: 'email', + tfaCode: tfaCode, refreshInfo: { refreshId, rememberMe, - expiresAt: new Date( - new Date().getTime() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000, - ), + expiresAt: new Date(new Date().getTime() + ms(String(REFRESH_TOKEN_EXPIRY))), }, } @@ -149,7 +150,7 @@ export const signUp = new mutationWithClientMutationId({ insertedUserCursor = await trx.step( () => query` WITH users - INSERT ${user} INTO users + INSERT ${user} INTO users RETURN MERGE( { id: NEW._key, @@ -163,6 +164,7 @@ export const signUp = new mutationWithClientMutationId({ 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.`)) } @@ -170,9 +172,8 @@ export const signUp = new mutationWithClientMutationId({ try { insertedUser = await insertedUserCursor.next() } catch (err) { - console.error( - `Cursor error occurred while user: ${userName} attempted to sign up, creating user: ${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.`)) } @@ -188,29 +189,23 @@ export const signUp = new mutationWithClientMutationId({ 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.`, - ) + 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.`, - ), + 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.`, - ) + 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.`, - ), + description: i18n._(t`Unable to sign up, please contact org admin for a new invite.`), } } @@ -223,7 +218,6 @@ export const signUp = new mutationWithClientMutationId({ _from: ${checkOrg._id}, _to: ${insertedUser._id}, permission: ${tokenRequestedRole}, - owner: false } INTO affiliations `, ) @@ -231,6 +225,7 @@ export const signUp = new mutationWithClientMutationId({ 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.`)) } } @@ -238,53 +233,40 @@ export const signUp = new mutationWithClientMutationId({ try { await trx.commit() } catch (err) { - console.error( - `Transaction commit error occurred while user: ${userName} attempted to sign up: ${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 }) - // 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), + const authenticateToken = tokenize({ + expiresIn: AUTH_TOKEN_EXPIRY, + parameters: { userKey: insertedUser._key }, + secret: String(SIGN_IN_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.`) + 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: 'authResult', - token, - user: returnUser, + _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 index 1a5564b753..78d07d6b6b 100644 --- a/api/src/user/mutations/update-user-password.js +++ b/api/src/user/mutations/update-user-password.js @@ -6,20 +6,18 @@ import { updateUserPasswordUnion } from '../unions' export const updateUserPassword = new mutationWithClientMutationId({ name: 'UpdateUserPassword', - description: - 'This mutation allows the user to update their account password.', + 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.', + type: new GraphQLNonNull(GraphQLString), + description: 'The users current password to verify it is the current user.', }, updatedPassword: { - type: GraphQLNonNull(GraphQLString), + type: new GraphQLNonNull(GraphQLString), description: 'The new password the user wishes to change to.', }, updatedPasswordConfirm: { - type: GraphQLNonNull(GraphQLString), + type: new GraphQLNonNull(GraphQLString), description: 'A password confirmation of their new password.', }, }), @@ -33,14 +31,7 @@ export const updateUserPassword = new mutationWithClientMutationId({ }), mutateAndGetPayload: async ( args, - { - i18n, - query, - collections, - transaction, - auth: { bcrypt, userRequired }, - validators: { cleanseInput }, - }, + { i18n, query, collections, transaction, auth: { bcrypt, userRequired }, validators: { cleanseInput } }, ) => { // Cleanse Input const currentPassword = cleanseInput(args.currentPassword) @@ -58,23 +49,17 @@ export const updateUserPassword = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to update password, current password does not match. Please try again.`, - ), + 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.`, - ) + 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.`, - ), + description: i18n._(t`Unable to update password, new passwords do not match. Please try again.`), } } @@ -86,9 +71,7 @@ export const updateUserPassword = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to update password, passwords do not match requirements. Please try again.`, - ), + description: i18n._(t`Unable to update password, passwords do not match requirements. Please try again.`), } } @@ -107,18 +90,16 @@ export const updateUserPassword = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Trx step error occurred when user: ${user._key} attempted to update their password: ${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}`, - ) + 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.`)) } diff --git a/api/src/user/mutations/update-user-profile.js b/api/src/user/mutations/update-user-profile.js index 497d8dacee..b7619fc81f 100644 --- a/api/src/user/mutations/update-user-profile.js +++ b/api/src/user/mutations/update-user-profile.js @@ -1,10 +1,13 @@ -import { GraphQLString } from 'graphql' +import { GraphQLString, GraphQLBoolean } from 'graphql' import { mutationWithClientMutationId } from 'graphql-relay' import { GraphQLEmailAddress } from 'graphql-scalars' import { t } from '@lingui/macro' -import { LanguageEnums, TfaSendMethodEnum } from '../../enums' +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', @@ -19,15 +22,18 @@ export const updateUserProfile = new mutationWithClientMutationId({ 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.', + }, + insideUser: { + type: GraphQLBoolean, + description: 'The updated boolean which represents if the user wants to see features in progress.', + }, + emailUpdateOptions: { + type: emailUpdatesInput, description: - 'The method in which the user wishes to have their TFA code sent via.', + 'A number of different emails the user can optionally receive periodically that provide updates about their organization.', }, }), outputFields: () => ({ @@ -56,19 +62,18 @@ export const updateUserProfile = new mutationWithClientMutationId({ // Cleanse Input const displayName = cleanseInput(args.displayName) const userName = cleanseInput(args.userName).toLowerCase() - const preferredLang = cleanseInput(args.preferredLang) 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 user name is already in use + // 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.`, - ) + console.warn(`User: ${userKey} attempted to update their username, but the username is already in use.`) return { _type: 'error', code: 400, @@ -83,18 +88,15 @@ export const updateUserProfile = new mutationWithClientMutationId({ let userAdmin try { userAdmin = await query` + WITH users, affiliations FOR v, e IN 1..1 INBOUND ${user._id} affiliations - FILTER e.permission == "admin" || e.permission == "super_admin" + 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.`), - ) + 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) { @@ -104,9 +106,7 @@ export const updateUserProfile = new mutationWithClientMutationId({ return { _type: 'error', code: 403, - description: i18n._( - t`Permission Denied: Multi-factor authentication is required for admin accounts`, - ), + description: i18n._(t`Permission Denied: Multi-factor authentication is required for admin accounts`), } } } @@ -116,29 +116,23 @@ export const updateUserProfile = new mutationWithClientMutationId({ tfaSendMethod = 'phone' } else if (subTfaSendMethod === 'email' && user.emailValidated) { tfaSendMethod = 'email' - } else if ( - subTfaSendMethod === 'none' || - typeof user.tfaSendMethod === 'undefined' - ) { + } 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 + // Create object containing updated data expect username. Username is handled separately for verification. const updatedUser = { displayName: displayName || user.displayName, - userName: userName || user.userName, - preferredLang: preferredLang || user.preferredLang, tfaSendMethod: tfaSendMethod, - emailValidated, + insideUser: typeof insideUserBool !== 'undefined' ? insideUserBool : user?.insideUser, + emailUpdateOptions: typeof emailUpdateOptions !== 'undefined' ? emailUpdateOptions : user?.emailUpdateOptions, } // Setup Transaction @@ -155,18 +149,16 @@ export const updateUserProfile = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Trx step error occurred when user: ${userKey} attempted to update their profile: ${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}`, - ) + 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.`)) } @@ -174,11 +166,20 @@ export const updateUserProfile = new mutationWithClientMutationId({ const returnUser = await loadUserByKey.load(userKey) if (changedUserName) { - const token = tokenize({ parameters: { userKey: returnUser._key } }) + 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({ user: returnUser, verifyUrl }) + await sendVerificationEmail({ + userName: userName, + displayName: returnUser.displayName, + verifyUrl: verifyUrl, + userKey: returnUser._key, + }) } console.info(`User: ${userKey} successfully updated their profile.`) diff --git a/api/src/user/mutations/verify-account.js b/api/src/user/mutations/verify-account.js index a2b16d546e..19fa3a894c 100644 --- a/api/src/user/mutations/verify-account.js +++ b/api/src/user/mutations/verify-account.js @@ -7,18 +7,17 @@ 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.', + 'This mutation allows the user to switch usernames/verify their account through a token sent in an email.', inputFields: () => ({ verifyTokenString: { - type: GraphQLNonNull(GraphQLString), + 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.', + description: '`VerifyAccountUnion` returning either a `VerifyAccountResult`, or `VerifyAccountError` object.', resolve: (payload) => payload, }, }), @@ -30,7 +29,8 @@ export const verifyAccount = new mutationWithClientMutationId({ collections, transaction, auth: { verifyToken }, - loaders: { loadUserByKey }, + loaders: { loadUserByKey, loadUserByUserName }, + notify: { sendUpdatedUserNameEmail }, validators: { cleanseInput }, }, ) => { @@ -48,30 +48,60 @@ export const verifyAccount = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to verify account. Please request a new email.`, - ), + 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 } = tokenParameters + 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.`, - ) + 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`Unable to verify account. Please request a new email.`, - ), + 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) @@ -81,36 +111,36 @@ export const verifyAccount = new mutationWithClientMutationId({ () => query` WITH users UPSERT { _key: ${user._key} } - INSERT { emailValidated: true } - UPDATE { emailValidated: true } + 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}`, - ) + 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}`, - ) + 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.`, - ) + 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.`, - ), + 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 index 43caa9cb0c..bcbf7d0fca 100644 --- a/api/src/user/mutations/verify-phone-number.js +++ b/api/src/user/mutations/verify-phone-number.js @@ -9,7 +9,7 @@ export const verifyPhoneNumber = new mutationWithClientMutationId({ description: 'This mutation allows the user to two factor authenticate.', inputFields: () => ({ twoFactorCode: { - type: GraphQLNonNull(GraphQLInt), + type: new GraphQLNonNull(GraphQLInt), description: 'The two factor code that was received via text message.', }, }), @@ -23,15 +23,7 @@ export const verifyPhoneNumber = new mutationWithClientMutationId({ }), mutateAndGetPayload: async ( args, - { - i18n, - userKey, - query, - collections, - transaction, - auth: { userRequired }, - loaders: { loadUserByKey }, - }, + { i18n, userKey, query, collections, transaction, auth: { userRequired }, loaders: { loadUserByKey } }, ) => { // Cleanse Input const twoFactorCode = args.twoFactorCode @@ -46,17 +38,13 @@ export const verifyPhoneNumber = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Two factor code length is incorrect. Please try again.`, - ), + 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.`, - ) + console.warn(`User: ${user._key} attempted to two factor authenticate, however the tfa codes do not match.`) return { _type: 'error', code: 400, @@ -79,38 +67,28 @@ export const verifyPhoneNumber = new mutationWithClientMutationId({ `, ) } 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.`), - ) + 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}`, - ) - throw new Error( - i18n._(t`Unable to two factor authenticate. Please try again.`), - ) + 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.`, - ) + 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.`, - ), + status: i18n._(t`Successfully verified phone number, and set TFA send method to text.`), } }, }) diff --git a/api/src/user/objects/__tests__/auth-result.test.js b/api/src/user/objects/__tests__/auth-result.test.js index 4b2fdaa8d6..7ce3e2734b 100644 --- a/api/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/src/user/objects/__tests__/authenticate-error.test.js b/api/src/user/objects/__tests__/authenticate-error.test.js index 982043fbf3..84f1b58c8b 100644 --- a/api/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/src/user/objects/__tests__/close-account-error.test.js b/api/src/user/objects/__tests__/close-account-error.test.js index d75a0336ce..fe2f13bcdc 100644 --- a/api/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/src/user/objects/__tests__/close-account-result.test.js b/api/src/user/objects/__tests__/close-account-result.test.js index a2f0d08035..59560abfe9 100644 --- a/api/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/src/user/objects/__tests__/remove-phone-number-error.test.js b/api/src/user/objects/__tests__/remove-phone-number-error.test.js index 18e849643f..189da64535 100644 --- a/api/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 index 5428d501d5..bdbd6558f0 100644 --- a/api/src/user/objects/__tests__/remove-phone-number-result.test.js +++ b/api/src/user/objects/__tests__/remove-phone-number-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { removePhoneNumberResultType } from '../remove-phone-number-result' +import {removePhoneNumberResultType} from '../remove-phone-number-result' describe('given the removePhoneNumberResultType object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the removePhoneNumberResultType object', () => { it('returns the resolved field', () => { const demoType = removePhoneNumberResultType.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__/reset-password-error.test.js b/api/src/user/objects/__tests__/reset-password-error.test.js index 4e779680cf..a55f081463 100644 --- a/api/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/src/user/objects/__tests__/reset-password-result.test.js b/api/src/user/objects/__tests__/reset-password-result.test.js index 351d768a0f..7165865254 100644 --- a/api/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/src/user/objects/__tests__/set-phone-number-error.test.js b/api/src/user/objects/__tests__/set-phone-number-error.test.js index 2d04fa3d54..8ec7d21bc8 100644 --- a/api/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/src/user/objects/__tests__/set-phone-number-result.test.js b/api/src/user/objects/__tests__/set-phone-number-result.test.js index 02cfc2156e..7c17fe4645 100644 --- a/api/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/src/user/objects/__tests__/sign-in-error.test.js b/api/src/user/objects/__tests__/sign-in-error.test.js index a87015743e..817b8f3a10 100644 --- a/api/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/src/user/objects/__tests__/sign-up-error.test.js b/api/src/user/objects/__tests__/sign-up-error.test.js index 150479b1ea..605ef0e4f9 100644 --- a/api/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/src/user/objects/__tests__/tfa-sign-in-result.test.js b/api/src/user/objects/__tests__/tfa-sign-in-result.test.js index 0eaf1b70ab..e4ff9f7caa 100644 --- a/api/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/src/user/objects/__tests__/update-user-password-error.test.js b/api/src/user/objects/__tests__/update-user-password-error.test.js index 2f2937ed24..495bc5dfb0 100644 --- a/api/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 index b4688b7e53..8722eb0717 100644 --- a/api/src/user/objects/__tests__/update-user-password-result.test.js +++ b/api/src/user/objects/__tests__/update-user-password-result.test.js @@ -1,6 +1,6 @@ -import { GraphQLString } from 'graphql' +import {GraphQLString} from 'graphql' -import { updateUserPasswordResultType } from '../update-user-password-result' +import {updateUserPasswordResultType} from '../update-user-password-result' describe('given the updateUserPasswordResultType object', () => { describe('testing the field definitions', () => { @@ -17,7 +17,7 @@ describe('given the updateUserPasswordResultType object', () => { it('returns the resolved field', () => { const demoType = updateUserPasswordResultType.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__/update-user-profile-error.test.js b/api/src/user/objects/__tests__/update-user-profile-error.test.js index a6c219860f..891899a7ac 100644 --- a/api/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/src/user/objects/__tests__/update-user-profile-result.test.js b/api/src/user/objects/__tests__/update-user-profile-result.test.js index aa16bd7554..201b14c2ce 100644 --- a/api/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 index 7b28f2112f..a5aeeeb708 100644 --- a/api/src/user/objects/__tests__/user-personal.test.js +++ b/api/src/user/objects/__tests__/user-personal.test.js @@ -1,16 +1,11 @@ import crypto from 'crypto' -import { - GraphQLNonNull, - GraphQLID, - GraphQLString, - GraphQLBoolean, -} from 'graphql' +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 { userPersonalType } from '../index' -import { LanguageEnums, TfaSendMethodEnum } from '../../../enums' +import { completedTour, dismissedMessage, userPersonalType } from '../index' +import { TfaSendMethodEnum } from '../../../enums' import { decryptPhoneNumber } from '../../../validators' const { CIPHER_KEY } = process.env @@ -21,7 +16,7 @@ describe('given the user object', () => { const demoType = userPersonalType.getFields() expect(demoType).toHaveProperty('id') - expect(demoType.id.type).toMatchObject(GraphQLNonNull(GraphQLID)) + expect(demoType.id.type).toMatchObject(new GraphQLNonNull(GraphQLID)) }) it('has a userName field', () => { const demoType = userPersonalType.getFields() @@ -41,12 +36,6 @@ describe('given the user object', () => { 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() @@ -69,9 +58,19 @@ describe('given the user object', () => { const demoType = userPersonalType.getFields() expect(demoType).toHaveProperty('affiliations') - expect(demoType.affiliations.type).toMatchObject( - affiliationConnection.connectionType, - ) + 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', () => { @@ -79,27 +78,21 @@ describe('given the user object', () => { it('returns the resolved field', () => { const demoType = userPersonalType.getFields() - expect(demoType.id.resolve({ id: '1' })).toEqual( - toGlobalId('user', '1'), - ) + 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') + 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') + expect(demoType.displayName.resolve({ displayName: 'display name' })).toEqual('display name') }) }) describe('testing the phoneNumber field', () => { @@ -146,14 +139,9 @@ describe('given the user object', () => { phoneNumber: '12345678912', } - const cipher = crypto.createCipheriv( - 'aes-256-ccm', - String(CIPHER_KEY), - Buffer.from(phoneDetails.iv, 'hex'), - { - authTagLength: 16, - }, - ) + 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') @@ -173,40 +161,25 @@ describe('given the user object', () => { }) }) }) - 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) + 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) + 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') + expect(demoType.tfaSendMethod.resolve({ tfaSendMethod: 'phone' })).toEqual('phone') }) }) describe('testing the affiliations field', () => { @@ -246,14 +219,74 @@ describe('given the user object', () => { { first: 1 }, { loaders: { - loadAffiliationConnectionsByUserId: jest - .fn() - .mockReturnValue(expectedResult), + 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/src/user/objects/__tests__/user-shared.test.js b/api/src/user/objects/__tests__/user-shared.test.js index 531df6088b..4f5248a53a 100644 --- a/api/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/src/user/objects/__tests__/verify-account-error.test.js b/api/src/user/objects/__tests__/verify-account-error.test.js index c4d9bcfea6..cff33f8ae9 100644 --- a/api/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/src/user/objects/__tests__/verify-account-result.test.js b/api/src/user/objects/__tests__/verify-account-result.test.js index d1996047b2..f37f10ac6c 100644 --- a/api/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/src/user/objects/__tests__/verify-phone-number-error.test.js b/api/src/user/objects/__tests__/verify-phone-number-error.test.js index 547bcc8fa6..be8fa8521d 100644 --- a/api/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/src/user/objects/__tests__/verify-phone-number-result.test.js b/api/src/user/objects/__tests__/verify-phone-number-result.test.js index 2a7fb6587b..c247e82a65 100644 --- a/api/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 index c5a2b8d68d..32b1e869f3 100644 --- a/api/src/user/objects/auth-result.js +++ b/api/src/user/objects/auth-result.js @@ -1,5 +1,5 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userPersonalType } from './user-personal' +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userPersonalType} from './user-personal' export const authResultType = new GraphQLObjectType({ name: 'AuthResult', @@ -8,12 +8,12 @@ export const authResultType = new GraphQLObjectType({ authToken: { type: GraphQLString, description: `JWT used for accessing controlled content.`, - resolve: ({ token }) => token, + resolve: ({token}) => token, }, user: { type: userPersonalType, description: `User that has just been created or signed in.`, - resolve: ({ user }) => user, + resolve: ({user}) => user, }, }), }) diff --git a/api/src/user/objects/authenticate-error.js b/api/src/user/objects/authenticate-error.js index 344c7ee291..66e741b03c 100644 --- a/api/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 index a85bc4f7ad..8bf44be0fc 100644 --- a/api/src/user/objects/close-account-error.js +++ b/api/src/user/objects/close-account-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const closeAccountError = new GraphQLObjectType({ name: 'CloseAccountError', @@ -8,12 +8,12 @@ export const closeAccountError = 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 encountered.', - resolve: ({ description }) => description, + resolve: ({description}) => description, }, }), }) diff --git a/api/src/user/objects/close-account-result.js b/api/src/user/objects/close-account-result.js index 960f38b951..e1bb4043d2 100644 --- a/api/src/user/objects/close-account-result.js +++ b/api/src/user/objects/close-account-result.js @@ -1,14 +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.', + 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 index 6edc31b0c3..79bb433a5f 100644 --- a/api/src/user/objects/index.js +++ b/api/src/user/objects/index.js @@ -2,6 +2,14 @@ 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' @@ -15,6 +23,7 @@ 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' 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/src/user/objects/remove-phone-number-error.js b/api/src/user/objects/remove-phone-number-error.js index b0a8011659..ffff03a8de 100644 --- a/api/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/src/user/objects/remove-phone-number-result.js b/api/src/user/objects/remove-phone-number-result.js index 7045e1ffec..9ffb7c5537 100644 --- a/api/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/src/user/objects/reset-password-error.js b/api/src/user/objects/reset-password-error.js index d0353e04ba..cbcba93ee5 100644 --- a/api/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/src/user/objects/reset-password-result.js b/api/src/user/objects/reset-password-result.js index 658ee9f284..13e340ac24 100644 --- a/api/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/src/user/objects/set-phone-number-error.js b/api/src/user/objects/set-phone-number-error.js index 0374905d0e..017ac5ef67 100644 --- a/api/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 index 9fc28bc6c1..3b8b6795dd 100644 --- a/api/src/user/objects/set-phone-number-result.js +++ b/api/src/user/objects/set-phone-number-result.js @@ -1,5 +1,5 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userPersonalType } from './user-personal' +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userPersonalType} from './user-personal' export const setPhoneNumberResultType = new GraphQLObjectType({ name: 'SetPhoneNumberResult', @@ -10,12 +10,12 @@ export const setPhoneNumberResultType = new GraphQLObjectType({ type: GraphQLString, description: 'Informs the user if their phone code was successfully sent.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, user: { type: userPersonalType, description: 'The user who set their phone number.', - resolve: ({ user }) => user, + resolve: ({user}) => user, }, }), }) diff --git a/api/src/user/objects/sign-in-error.js b/api/src/user/objects/sign-in-error.js index 4afb152e7b..a3037d43bf 100644 --- a/api/src/user/objects/sign-in-error.js +++ b/api/src/user/objects/sign-in-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const signInError = new GraphQLObjectType({ name: 'SignInError', @@ -8,12 +8,12 @@ export const signInError = 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/sign-up-error.js b/api/src/user/objects/sign-up-error.js index e45637a675..eeb87064e5 100644 --- a/api/src/user/objects/sign-up-error.js +++ b/api/src/user/objects/sign-up-error.js @@ -1,4 +1,4 @@ -import { GraphQLInt, GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLInt, GraphQLObjectType, GraphQLString} from 'graphql' export const signUpError = new GraphQLObjectType({ name: 'SignUpError', @@ -8,12 +8,12 @@ export const signUpError = 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/tfa-sign-in-result.js b/api/src/user/objects/tfa-sign-in-result.js index e880d8b55f..08bd0053d5 100644 --- a/api/src/user/objects/tfa-sign-in-result.js +++ b/api/src/user/objects/tfa-sign-in-result.js @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' export const tfaSignInResult = new GraphQLObjectType({ name: 'TFASignInResult', @@ -8,13 +8,13 @@ export const tfaSignInResult = new GraphQLObjectType({ authenticateToken: { type: GraphQLString, description: 'Token used to verify during authentication.', - resolve: ({ authenticateToken }) => authenticateToken, + resolve: ({authenticateToken}) => authenticateToken, }, sendMethod: { type: GraphQLString, description: 'Whether the authentication code was sent through text, or email.', - resolve: ({ sendMethod }) => sendMethod, + resolve: ({sendMethod}) => sendMethod, }, }), }) diff --git a/api/src/user/objects/update-user-password-error.js b/api/src/user/objects/update-user-password-error.js index 7708ba6106..c52e6cf611 100644 --- a/api/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/src/user/objects/update-user-password-result.js b/api/src/user/objects/update-user-password-result.js index 4ab81653f2..ed471795e9 100644 --- a/api/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/src/user/objects/update-user-profile-error.js b/api/src/user/objects/update-user-profile-error.js index 10123abda2..93e56f9af2 100644 --- a/api/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 index 27df736e00..cdb01ee1e0 100644 --- a/api/src/user/objects/update-user-profile-result.js +++ b/api/src/user/objects/update-user-profile-result.js @@ -1,5 +1,5 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { userPersonalType } from './user-personal' +import {GraphQLObjectType, GraphQLString} from 'graphql' +import {userPersonalType} from './user-personal' export const updateUserProfileResultType = new GraphQLObjectType({ name: 'UpdateUserProfileResult', @@ -10,12 +10,12 @@ export const updateUserProfileResultType = new GraphQLObjectType({ type: GraphQLString, description: 'Informs the user if the password reset was successful, and to redirect to sign in page.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, user: { type: userPersonalType, description: 'Return the newly updated user information.', - resolve: ({ user }) => user, + 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 index b2ecd376a1..453160df06 100644 --- a/api/src/user/objects/user-personal.js +++ b/api/src/user/objects/user-personal.js @@ -1,11 +1,14 @@ -import { GraphQLBoolean, GraphQLObjectType, GraphQLString } from 'graphql' +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 { LanguageEnums, TfaSendMethodEnum } from '../../enums' +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', @@ -24,22 +27,13 @@ export const userPersonalType = new GraphQLObjectType({ phoneNumber: { type: GraphQLPhoneNumber, description: 'The phone number the user has setup with tfa.', - resolve: ( - { phoneDetails }, - _args, - { validators: { decryptPhoneNumber } }, - ) => { + 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.', @@ -55,6 +49,17 @@ export const userPersonalType = new GraphQLObjectType({ 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.', @@ -69,11 +74,7 @@ export const userPersonalType = new GraphQLObjectType({ }, ...connectionArgs, }, - resolve: async ( - { _id }, - args, - { loaders: { loadAffiliationConnectionsByUserId } }, - ) => { + resolve: async ({ _id }, args, { loaders: { loadAffiliationConnectionsByUserId } }) => { const affiliations = await loadAffiliationConnectionsByUserId({ userId: _id, ...args, @@ -81,6 +82,16 @@ export const userPersonalType = new GraphQLObjectType({ 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, diff --git a/api/src/user/objects/user-shared.js b/api/src/user/objects/user-shared.js index 99e0330dda..188c3c0e5c 100644 --- a/api/src/user/objects/user-shared.js +++ b/api/src/user/objects/user-shared.js @@ -1,8 +1,10 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' -import { globalIdField } from 'graphql-relay' -import { GraphQLEmailAddress } from 'graphql-scalars' +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' +import {nodeInterface} from '../../node' export const userSharedType = new GraphQLObjectType({ name: 'SharedUser', @@ -11,15 +13,51 @@ export const userSharedType = new GraphQLObjectType({ displayName: { type: GraphQLString, description: 'Users display name.', - resolve: ({ displayName }) => displayName, + resolve: ({displayName}) => displayName, }, userName: { type: GraphQLEmailAddress, description: 'Users email address.', - resolve: ({ userName }) => userName, + 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, + 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/src/user/objects/verify-account-error.js b/api/src/user/objects/verify-account-error.js index a059781f43..9f829797b0 100644 --- a/api/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/src/user/objects/verify-account-result.js b/api/src/user/objects/verify-account-result.js index 4b5560e12c..7bc423b81f 100644 --- a/api/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/src/user/objects/verify-phone-number-error.js b/api/src/user/objects/verify-phone-number-error.js index 37adde6437..a21a1b4047 100644 --- a/api/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 index c6c01c5827..b9f3e42f87 100644 --- a/api/src/user/objects/verify-phone-number-result.js +++ b/api/src/user/objects/verify-phone-number-result.js @@ -1,6 +1,6 @@ -import { GraphQLObjectType, GraphQLString } from 'graphql' +import {GraphQLObjectType, GraphQLString} from 'graphql' -import { userPersonalType } from './user-personal' +import {userPersonalType} from './user-personal' export const verifyPhoneNumberResultType = new GraphQLObjectType({ name: 'VerifyPhoneNumberResult', @@ -11,12 +11,12 @@ export const verifyPhoneNumberResultType = new GraphQLObjectType({ type: GraphQLString, description: 'Informs the user if their phone number was successfully verified.', - resolve: ({ status }) => status, + resolve: ({status}) => status, }, user: { type: userPersonalType, description: 'The user who verified their phone number.', - resolve: ({ user }) => user, + resolve: ({user}) => user, }, }), }) diff --git a/api/src/user/queries/__tests__/find-me.test.js b/api/src/user/queries/__tests__/find-me.test.js index f20c1804ca..68d8a5ad98 100644 --- a/api/src/user/queries/__tests__/find-me.test.js +++ b/api/src/user/queries/__tests__/find-me.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 } from 'graphql' import { toGlobalId } from 'graphql-relay' @@ -23,16 +24,16 @@ describe('given the findMe query', () => { }) // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) let consoleOutput = [] @@ -48,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, }) @@ -65,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, @@ -83,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/src/user/queries/__tests__/find-user-by-username.test.js b/api/src/user/queries/__tests__/find-user-by-username.test.js index adeb69e41b..6006d15417 100644 --- a/api/src/user/queries/__tests__/find-user-by-username.test.js +++ b/api/src/user/queries/__tests__/find-user-by-username.test.js @@ -1,5 +1,6 @@ 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' @@ -40,29 +41,27 @@ describe('given the findUserByUsername query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + 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, }) @@ -124,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" @@ -136,8 +135,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -158,7 +157,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -185,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" @@ -197,8 +196,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -219,7 +218,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -261,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" @@ -273,8 +272,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -295,7 +294,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -322,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" @@ -334,8 +333,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: user._key, query: query, @@ -356,7 +355,7 @@ describe('given the findUserByUsername query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -390,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" @@ -402,8 +401,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -422,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" @@ -442,8 +439,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -462,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.`]) }) }) }) @@ -490,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" @@ -502,8 +497,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -522,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" @@ -542,8 +535,8 @@ describe('given the findUserByUsername query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, userKey: 123, query: query, @@ -562,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/src/user/queries/__tests__/is-user-admin.test.js b/api/src/user/queries/__tests__/is-user-admin.test.js index f9ef3b5be3..3328d3ec5e 100644 --- a/api/src/user/queries/__tests__/is-user-admin.test.js +++ b/api/src/user/queries/__tests__/is-user-admin.test.js @@ -1,5 +1,6 @@ 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' @@ -40,22 +41,21 @@ describe('given the isUserAdmin query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -109,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: { @@ -135,7 +135,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -156,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: { @@ -182,7 +182,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -203,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: { @@ -229,7 +229,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -252,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: { @@ -278,7 +278,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -299,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: { @@ -325,7 +325,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -346,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: { @@ -372,7 +372,7 @@ describe('given the isUserAdmin query', () => { cleanseInput, }, }, - ) + }) const expectedResponse = { data: { @@ -402,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({ @@ -432,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.`, @@ -462,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({ @@ -492,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/src/user/queries/__tests__/is-user-super-admin.test.js b/api/src/user/queries/__tests__/is-user-super-admin.test.js index f0974b0a12..48dfa1cf39 100644 --- a/api/src/user/queries/__tests__/is-user-super-admin.test.js +++ b/api/src/user/queries/__tests__/is-user-super-admin.test.js @@ -1,5 +1,6 @@ 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 { checkPermission, userRequired } from '../../../auth' @@ -37,22 +38,21 @@ describe('given the isUserSuperAdmin query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { await collections.users.save({ userName: 'test.account@istio.actually.exists', displayName: 'Test Account', - preferredLang: 'french', tfaValidated: false, emailValidated: false, }) @@ -104,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: { @@ -126,7 +126,7 @@ describe('given the isUserSuperAdmin query', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -147,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: { @@ -169,7 +169,7 @@ describe('given the isUserSuperAdmin query', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -190,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: { @@ -212,7 +212,7 @@ describe('given the isUserSuperAdmin query', () => { loadUserByKey: loadUserByKey({ query }), }, }, - ) + }) const expectedResponse = { data: { @@ -241,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({ @@ -263,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.`, @@ -294,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({ @@ -316,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 index a53b4881bc..f5426dff48 100644 --- a/api/src/user/queries/find-me.js +++ b/api/src/user/queries/find-me.js @@ -1,9 +1,9 @@ -import { userPersonalType } from '../objects' +import {userPersonalType} from '../objects' export const findMe = { type: userPersonalType, description: 'Query the currently logged in user.', - resolve: async (_, __, { auth: { userRequired } }) => { + resolve: async (_, __, {auth: {userRequired}}) => { // Get querying user const user = await userRequired() 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/src/user/queries/find-user-by-username.js b/api/src/user/queries/find-user-by-username.js index ae1aa5d799..dcd8c3c986 100644 --- a/api/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 index 0d77b90779..6fb8a36b56 100644 --- a/api/src/user/queries/index.js +++ b/api/src/user/queries/index.js @@ -1,4 +1,6 @@ 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' diff --git a/api/src/user/queries/is-user-admin.js b/api/src/user/queries/is-user-admin.js index ea0fbae271..108947b7a2 100644 --- a/api/src/user/queries/is-user-admin.js +++ b/api/src/user/queries/is-user-admin.js @@ -8,8 +8,7 @@ export const isUserAdmin = { args: { orgId: { type: GraphQLID, - description: - 'Optional org id to see if user is an admin for the requested org.', + description: 'Optional org id to see if user is an admin for the requested org.', }, }, resolve: async ( @@ -25,44 +24,31 @@ export const isUserAdmin = { }, ) => { const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) - const user = await userRequired() // check if for a specific org - if (orgKey !== '') { + 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 + 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` - FOR v, e IN 1..1 INBOUND ${user._id} affiliations - FILTER e.permission == "admin" || e.permission == "super_admin" - LIMIT 1 + 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) { - return true + 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 false + 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 index e8a26dc78f..f634f08ac9 100644 --- a/api/src/user/queries/is-user-super-admin.js +++ b/api/src/user/queries/is-user-super-admin.js @@ -1,18 +1,19 @@ -import { GraphQLBoolean } from 'graphql' -import { t } from '@lingui/macro' +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 } }) => { + 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 + 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) { diff --git a/api/src/user/queries/login-required.js b/api/src/user/queries/login-required.js index f20253e58a..6d4013cabe 100644 --- a/api/src/user/queries/login-required.js +++ b/api/src/user/queries/login-required.js @@ -1,9 +1,9 @@ -import { GraphQLBoolean } from 'graphql' +import {GraphQLBoolean} from 'graphql' export const loginRequired = { type: GraphQLBoolean, description: 'Checks if user must be logged in to access data.', - resolve: async (_, __, { auth: { loginRequiredBool } }) => { + resolve: async (_, __, {auth: {loginRequiredBool}}) => { return loginRequiredBool }, } diff --git a/api/src/user/unions/__tests__/authenticate-union.test.js b/api/src/user/unions/__tests__/authenticate-union.test.js index 6fffa1721a..8b035a7e1a 100644 --- a/api/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/src/user/unions/__tests__/close-account-union.test.js b/api/src/user/unions/__tests__/close-account-union.test.js index 9544ab2a3f..20d97773ca 100644 --- a/api/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/src/user/unions/__tests__/refresh-tokens-union.test.js b/api/src/user/unions/__tests__/refresh-tokens-union.test.js index dd75e4238a..803f987ff5 100644 --- a/api/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/src/user/unions/__tests__/remove-phone-number-union.test.js b/api/src/user/unions/__tests__/remove-phone-number-union.test.js index cb1f7d3a84..01e1a577f5 100644 --- a/api/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/src/user/unions/__tests__/reset-password-union.test.js b/api/src/user/unions/__tests__/reset-password-union.test.js index fc6b2ebf4a..0e3132cb81 100644 --- a/api/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/src/user/unions/__tests__/set-phone-number-union.test.js b/api/src/user/unions/__tests__/set-phone-number-union.test.js index 730d83b32d..05eca64b68 100644 --- a/api/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/src/user/unions/__tests__/sign-in-union.test.js b/api/src/user/unions/__tests__/sign-in-union.test.js index 689c0d9e68..a54349d56a 100644 --- a/api/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 index 20c1e887b8..d513109381 100644 --- a/api/src/user/unions/__tests__/sign-up-union.test.js +++ b/api/src/user/unions/__tests__/sign-up-union.test.js @@ -1,12 +1,12 @@ -import { authResultType, signUpError } from '../../objects/index' +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 authResultType type', () => { + it('contains tfaSignInResult type', () => { const demoType = signUpUnion.getTypes() - expect(demoType).toContain(authResultType) + expect(demoType).toContain(tfaSignInResult) }) it('contains signUpError type', () => { const demoType = signUpUnion.getTypes() @@ -18,11 +18,11 @@ describe('given the sign up union', () => { describe('testing the authResult type', () => { it('returns the correct type', () => { const obj = { - _type: 'authResult', + _type: 'tfa', authResult: {}, } - expect(signUpUnion.resolveType(obj)).toMatchObject(authResultType) + expect(signUpUnion.resolveType(obj)).toMatch(tfaSignInResult.name) }) }) describe('testing the signUpError type', () => { @@ -34,7 +34,7 @@ describe('given the sign up union', () => { description: 'text', } - expect(signUpUnion.resolveType(obj)).toMatchObject(signUpError) + expect(signUpUnion.resolveType(obj)).toMatch(signUpError.name) }) }) }) diff --git a/api/src/user/unions/__tests__/update-user-password-union.test.js b/api/src/user/unions/__tests__/update-user-password-union.test.js index 850aa2a905..96d88f2ee8 100644 --- a/api/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/src/user/unions/__tests__/update-user-profile-union.test.js b/api/src/user/unions/__tests__/update-user-profile-union.test.js index fe302fa264..152da5a5f7 100644 --- a/api/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/src/user/unions/__tests__/verify-account-union.test.js b/api/src/user/unions/__tests__/verify-account-union.test.js index e100d222a5..5b4b5e3830 100644 --- a/api/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/src/user/unions/__tests__/verify-phone-number-union.test.js b/api/src/user/unions/__tests__/verify-phone-number-union.test.js index 6abd158ee8..e86daea476 100644 --- a/api/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/src/user/unions/authenticate-union.js b/api/src/user/unions/authenticate-union.js index 0d42f86ad0..8d9e56a822 100644 --- a/api/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 index 16aecaf7ab..be84f80337 100644 --- a/api/src/user/unions/close-account-union.js +++ b/api/src/user/unions/close-account-union.js @@ -3,14 +3,13 @@ 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.', + 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 + return closeAccountError.name } else { - return closeAccountResult + 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 index 1a94618ed7..a116a965b4 100644 --- a/api/src/user/unions/index.js +++ b/api/src/user/unions/index.js @@ -1,5 +1,7 @@ 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' diff --git a/api/src/user/unions/refresh-tokens-union.js b/api/src/user/unions/refresh-tokens-union.js index 17d3439157..9287e20d25 100644 --- a/api/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 index 1af22a5d97..36b9f995dd 100644 --- a/api/src/user/unions/remove-phone-number-union.js +++ b/api/src/user/unions/remove-phone-number-union.js @@ -1,8 +1,5 @@ import { GraphQLUnionType } from 'graphql' -import { - removePhoneNumberErrorType, - removePhoneNumberResultType, -} from '../objects' +import { removePhoneNumberErrorType, removePhoneNumberResultType } from '../objects' export const removePhoneNumberUnion = new GraphQLUnionType({ name: 'RemovePhoneNumberUnion', @@ -11,9 +8,9 @@ export const removePhoneNumberUnion = new GraphQLUnionType({ types: [removePhoneNumberErrorType, removePhoneNumberResultType], resolveType({ _type }) { if (_type === 'result') { - return removePhoneNumberResultType + return removePhoneNumberResultType.name } else { - return removePhoneNumberErrorType + return removePhoneNumberErrorType.name } }, }) diff --git a/api/src/user/unions/reset-password-union.js b/api/src/user/unions/reset-password-union.js index ef85cb1e3f..b1795fe5eb 100644 --- a/api/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/src/user/unions/set-phone-number-union.js b/api/src/user/unions/set-phone-number-union.js index 1b1313f6e3..d38fe063e7 100644 --- a/api/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/src/user/unions/sign-in-union.js b/api/src/user/unions/sign-in-union.js index 7dacaf1807..50de60f116 100644 --- a/api/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 index 72b4ec39dd..90f738b8d6 100644 --- a/api/src/user/unions/sign-up-union.js +++ b/api/src/user/unions/sign-up-union.js @@ -1,16 +1,16 @@ import { GraphQLUnionType } from 'graphql' -import { authResultType, signUpError } from '../objects' +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: [authResultType, signUpError], + types: [tfaSignInResult, signUpError], resolveType({ _type }) { - if (_type === 'authResult') { - return authResultType + if (_type === 'tfa') { + return tfaSignInResult.name } else { - return signUpError + 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 index f5b63b38b8..15e32a3adf 100644 --- a/api/src/user/unions/update-user-password-union.js +++ b/api/src/user/unions/update-user-password-union.js @@ -1,8 +1,5 @@ import { GraphQLUnionType } from 'graphql' -import { - updateUserPasswordErrorType, - updateUserPasswordResultType, -} from '../objects' +import { updateUserPasswordErrorType, updateUserPasswordResultType } from '../objects' export const updateUserPasswordUnion = new GraphQLUnionType({ name: 'UpdateUserPasswordUnion', @@ -11,9 +8,9 @@ export const updateUserPasswordUnion = new GraphQLUnionType({ types: [updateUserPasswordErrorType, updateUserPasswordResultType], resolveType({ _type }) { if (_type === 'regular') { - return updateUserPasswordResultType + return updateUserPasswordResultType.name } else { - return updateUserPasswordErrorType + 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 index ed95177ce6..00287b56e3 100644 --- a/api/src/user/unions/update-user-profile-union.js +++ b/api/src/user/unions/update-user-profile-union.js @@ -1,8 +1,5 @@ import { GraphQLUnionType } from 'graphql' -import { - updateUserProfileErrorType, - updateUserProfileResultType, -} from '../objects' +import { updateUserProfileErrorType, updateUserProfileResultType } from '../objects' export const updateUserProfileUnion = new GraphQLUnionType({ name: 'UpdateUserProfileUnion', @@ -11,9 +8,9 @@ export const updateUserProfileUnion = new GraphQLUnionType({ types: [updateUserProfileErrorType, updateUserProfileResultType], resolveType({ _type }) { if (_type === 'success') { - return updateUserProfileResultType + return updateUserProfileResultType.name } else { - return updateUserProfileErrorType + return updateUserProfileErrorType.name } }, }) diff --git a/api/src/user/unions/verify-account-union.js b/api/src/user/unions/verify-account-union.js index b509bcefe1..b44f12c7a3 100644 --- a/api/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 index 16ab4e3845..61fb2def55 100644 --- a/api/src/user/unions/verify-phone-number-union.js +++ b/api/src/user/unions/verify-phone-number-union.js @@ -1,8 +1,5 @@ import { GraphQLUnionType } from 'graphql' -import { - verifyPhoneNumberErrorType, - verifyPhoneNumberResultType, -} from '../objects' +import { verifyPhoneNumberErrorType, verifyPhoneNumberResultType } from '../objects' export const verifyPhoneNumberUnion = new GraphQLUnionType({ name: 'VerifyPhoneNumberUnion', @@ -11,9 +8,9 @@ export const verifyPhoneNumberUnion = new GraphQLUnionType({ types: [verifyPhoneNumberErrorType, verifyPhoneNumberResultType], resolveType({ _type }) { if (_type === 'success') { - return verifyPhoneNumberResultType + return verifyPhoneNumberResultType.name } else { - return verifyPhoneNumberErrorType + return verifyPhoneNumberErrorType.name } }, }) diff --git a/api/src/validators/__tests__/cleanse-input.test.js b/api/src/validators/__tests__/cleanse-input.test.js index 4bd17b5ca3..6f9e40904c 100644 --- a/api/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/src/validators/__tests__/decrypt-phone-number.test.js b/api/src/validators/__tests__/decrypt-phone-number.test.js index 1105d2a81a..6cc7ca8108 100644 --- a/api/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/src/validators/__tests__/slugify.test.js b/api/src/validators/__tests__/slugify.test.js index 5d5e9678bc..c99d00ada3 100644 --- a/api/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 index cc068505bd..6f3942df15 100644 --- a/api/src/validators/cleanse-input.js +++ b/api/src/validators/cleanse-input.js @@ -14,7 +14,6 @@ const customEscape = (str) => { return str .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 index caa374a608..b80ed1268e 100644 --- a/api/src/validators/decrypt-phone-number.js +++ b/api/src/validators/decrypt-phone-number.js @@ -1,13 +1,13 @@ import crypto from 'crypto' -const { CIPHER_KEY } = process.env +const {CIPHER_KEY} = process.env -export const decryptPhoneNumber = ({ iv, tag, phoneNumber: encrypted }) => { +export const decryptPhoneNumber = ({iv, tag, phoneNumber: encrypted}) => { const decipher = crypto.createDecipheriv( 'aes-256-ccm', String(CIPHER_KEY), Buffer.from(iv, 'hex'), - { authTagLength: 16 }, + {authTagLength: 16}, ) decipher.setAuthTag(Buffer.from(tag, 'hex')) let decrypted = decipher.update(encrypted, 'hex', 'utf8') 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 index b507d7fcf9..a03bd7ef2e 100644 --- a/api/src/verified-domains/inputs/__tests__/verified-domain-order.test.js +++ b/api/src/verified-domains/inputs/__tests__/verified-domain-order.test.js @@ -9,17 +9,13 @@ describe('given the verifiedDomainOrder input object', () => { const demoType = verifiedDomainOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(VerifiedDomainOrderField), - ) + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(VerifiedDomainOrderField)) }) }) }) diff --git a/api/src/verified-domains/inputs/verified-domain-order.js b/api/src/verified-domains/inputs/verified-domain-order.js index f963c57bda..d6d4d8851e 100644 --- a/api/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/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 index a1afd825de..55ea1176cf 100644 --- a/api/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,4 +1,5 @@ -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' @@ -22,16 +23,16 @@ describe('given a loadVerifiedDomainsById dataloader', () => { 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, - })) + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) }) beforeEach(async () => { org = await collections.organizations.save({ @@ -135,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([ @@ -166,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([ @@ -194,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([ @@ -227,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/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 index f5f485a18b..0fc8226a6c 100644 --- a/api/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,4 +1,5 @@ -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' @@ -22,16 +23,16 @@ describe('given a loadVerifiedDomainByKey dataloader', () => { 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, - })) + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) }) beforeEach(async () => { org = await collections.organizations.save({ @@ -135,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([ @@ -166,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([ @@ -194,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([ @@ -227,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/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 index fdb966208a..c956952d34 100644 --- a/api/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,15 +1,13 @@ 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 { 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,22 +28,21 @@ describe('given the loadVerifiedDomainConnectionsByOrgId function', () => { 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, - })) + 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, }) @@ -108,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 @@ -154,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 @@ -200,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 @@ -245,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 @@ -1110,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([ @@ -1139,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([ @@ -1215,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, @@ -1234,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.`, @@ -1248,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, @@ -1267,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.`, @@ -1284,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, @@ -1303,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([ @@ -1339,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([ @@ -1442,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([ @@ -1471,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([ @@ -1547,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, @@ -1567,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([ @@ -1580,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, @@ -1600,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([ @@ -1616,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, @@ -1635,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([ @@ -1673,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/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js b/api/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js index cba8bbeec9..c5d31a5488 100644 --- a/api/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,15 +1,13 @@ 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 { 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,22 +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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) + 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, }) @@ -108,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 @@ -153,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 @@ -198,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 @@ -242,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 @@ -1088,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([ @@ -1116,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([ @@ -1190,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, @@ -1208,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.`, @@ -1222,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, @@ -1240,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.`, @@ -1257,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, @@ -1275,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([ @@ -1311,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([ @@ -1411,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([ @@ -1439,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([ @@ -1513,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, @@ -1532,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([ @@ -1545,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, @@ -1564,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([ @@ -1580,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, @@ -1598,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([ @@ -1635,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/src/verified-domains/objects/__tests__/verified-domain.test.js b/api/src/verified-domains/objects/__tests__/verified-domain.test.js index b3dd32d579..f001a14da0 100644 --- a/api/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/src/verified-domains/objects/verified-domain.js b/api/src/verified-domains/objects/verified-domain.js index 624ce489e7..281eba3768 100644 --- a/api/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/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 index a041bad02b..c9a4463a0a 100644 --- a/api/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,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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -51,16 +52,16 @@ describe('given findVerifiedDomainByDomain query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { org = await collections.organizations.save({ @@ -112,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: { @@ -130,11 +131,10 @@ describe('given findVerifiedDomainByDomain query', () => { }, loaders: { loadVerifiedDomainsById: loadVerifiedDomainsById({ query }), - loadVerifiedOrgConnectionsByDomainId: - loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), + loadVerifiedOrgConnectionsByDomainId: loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), }, }, - ) + }) const expectedResponse = { data: { @@ -165,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: { @@ -187,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) }) @@ -218,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: { @@ -240,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/src/verified-domains/queries/__tests__/find-verified-domains.test.js b/api/src/verified-domains/queries/__tests__/find-verified-domains.test.js index 0c3b88ffc6..6857181e3b 100644 --- a/api/src/verified-domains/queries/__tests__/find-verified-domains.test.js +++ b/api/src/verified-domains/queries/__tests__/find-verified-domains.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 } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -51,16 +52,16 @@ describe('given findVerifiedDomains query', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { org = await collections.organizations.save({ @@ -112,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 { @@ -133,8 +134,8 @@ describe('given findVerifiedDomains query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -145,11 +146,10 @@ describe('given findVerifiedDomains query', () => { query, cleanseInput, }), - loadVerifiedOrgConnectionsByDomainId: - loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), + loadVerifiedOrgConnectionsByDomainId: loadVerifiedOrgConnectionsByDomainId(query, 'en', cleanseInput), }, }, - ) + }) const expectedResponse = { data: { @@ -179,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 { @@ -200,8 +200,8 @@ describe('given findVerifiedDomains query', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -220,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 index 80e159f3b8..0100b1d4a0 100644 --- a/api/src/verified-domains/queries/find-verified-domain-by-domain.js +++ b/api/src/verified-domains/queries/find-verified-domain-by-domain.js @@ -8,19 +8,11 @@ export const findVerifiedDomainByDomain = { description: 'Retrieve a specific verified domain by providing a domain.', args: { domain: { - type: GraphQLNonNull(Domain), + type: new GraphQLNonNull(Domain), description: 'The domain you wish to retrieve information for.', }, }, - resolve: async ( - _, - args, - { - i18n, - loaders: { loadVerifiedDomainsById }, - validators: { cleanseInput }, - }, - ) => { + resolve: async (_, args, { i18n, loaders: { loadVerifiedDomainsById }, validators: { cleanseInput } }) => { // Cleanse input const domainInput = cleanseInput(args.domain) @@ -29,9 +21,7 @@ export const findVerifiedDomainByDomain = { 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.`), - ) + throw new Error(i18n._(t`No verified domain with the provided domain could be found.`)) } domain.id = domain._key 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 index 69612d1e7c..b0123540a1 100644 --- a/api/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js +++ b/api/src/verified-organizations/inputs/__tests__/verified-organization-order.test.js @@ -9,17 +9,13 @@ describe('given the verifiedOrganizationOrder input object', () => { const demoType = verifiedOrganizationOrder.getFields() expect(demoType).toHaveProperty('direction') - expect(demoType.direction.type).toMatchObject( - GraphQLNonNull(OrderDirection), - ) + 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( - GraphQLNonNull(VerifiedOrganizationOrderField), - ) + expect(demoType.field.type).toMatchObject(new GraphQLNonNull(VerifiedOrganizationOrderField)) }) }) }) diff --git a/api/src/verified-organizations/inputs/verified-organization-order.js b/api/src/verified-organizations/inputs/verified-organization-order.js index cd2f11ccb7..dff98ecd79 100644 --- a/api/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/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 index a1321e4bbf..2419fbb120 100644 --- a/api/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,15 +1,13 @@ 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 { 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 @@ -27,20 +25,21 @@ const i18n = setupI18n({ }, }) +const orgOneSummaryData = { + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, +} + const orgOneData = { verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'slug-org-one', @@ -65,20 +64,21 @@ const orgOneData = { }, } +const orgTwoSummaryData = { + web: { + pass: 52, + fail: 1002, + total: 1054, + }, + mail: { + pass: 52, + fail: 1002, + total: 1054, + }, +} + const orgTwoData = { verified: true, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, orgDetails: { en: { slug: 'slug-org-two', @@ -103,20 +103,21 @@ const orgTwoData = { }, } +const orgThreeSummaryData = { + web: { + pass: 51, + fail: 1001, + total: 1052, + }, + mail: { + pass: 51, + fail: 1001, + total: 1052, + }, +} + const orgThreeData = { verified: true, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, orgDetails: { en: { slug: 'slug-org-three', @@ -163,32 +164,34 @@ 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, db, org, orgTwo, domain, domainTwo, domainThree beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { org = await collections.organizations.save(orgOneData) + const orgSummary = await collections.organizationSummaries.save({ + organization: org._id, + ...orgOneSummaryData, + }) + await db.collection("organizations").update(org._key, { latestSummaryId: orgSummary._id }) orgTwo = await collections.organizations.save(orgTwoData) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + ...orgTwoSummaryData, + }) + await db.collection("organizations").update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) domain = await collections.domains.save(domainOneData) domainTwo = await collections.domains.save(domainTwoData) @@ -249,10 +252,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], }, @@ -262,14 +262,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), }, } @@ -283,30 +277,32 @@ 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, db, org, orgTwo, domain, domainTwo, domainThree beforeEach(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) org = await collections.organizations.save(orgOneData) + const orgSummary = await collections.organizationSummaries.save({ + organization: org._id, + ...orgOneSummaryData, + }) + await db.collection("organizations").update(org._key, { latestSummaryId: orgSummary._id }) orgTwo = await collections.organizations.save(orgTwoData) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + ...orgTwoSummaryData, + }) + await db.collection("organizations").update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) domain = await collections.domains.save(domainOneData) domainTwo = await collections.domains.save(domainTwoData) @@ -367,10 +363,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], }, @@ -380,14 +373,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), }, } @@ -401,30 +388,32 @@ 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, db, org, orgTwo, domain, domainTwo, domainThree beforeEach(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) org = await collections.organizations.save(orgOneData) + const orgSummary = await collections.organizationSummaries.save({ + organization: org._id, + ...orgOneSummaryData, + }) + await db.collection("organizations").update(org._key, { latestSummaryId: orgSummary._id }) orgTwo = await collections.organizations.save(orgTwoData) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + ...orgTwoSummaryData, + }) + await db.collection("organizations").update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) domain = await collections.domains.save(domainOneData) domainTwo = await collections.domains.save(domainTwoData) @@ -484,10 +473,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], }, @@ -497,14 +483,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), }, } @@ -518,30 +498,32 @@ 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, db, org, orgTwo, domain, domainTwo, domainThree beforeEach(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) org = await collections.organizations.save(orgOneData) + const orgSummary = await collections.organizationSummaries.save({ + organization: org._id, + ...orgOneSummaryData, + }) + await db.collection("organizations").update(org._key, { latestSummaryId: orgSummary._id }) orgTwo = await collections.organizations.save(orgTwoData) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + ...orgTwoSummaryData, + }) + await db.collection("organizations").update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) domain = await collections.domains.save(domainOneData) domainTwo = await collections.domains.save(domainTwoData) @@ -601,10 +583,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], }, @@ -614,14 +593,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), }, } @@ -635,33 +608,39 @@ 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, db, org, orgTwo, domain, domainTwo, domainThree, orgThree beforeEach(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) org = await collections.organizations.save(orgOneData) + const orgSummary = await collections.organizationSummaries.save({ + organization: org._id, + ...orgOneSummaryData, + }) + await db.collection("organizations").update(org._key, { latestSummaryId: orgSummary._id }) orgTwo = await collections.organizations.save(orgTwoData) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + ...orgTwoSummaryData, + }) + await db.collection("organizations").update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) orgThree = await collections.organizations.save(orgThreeData) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + ...orgThreeSummaryData, + }) + await db.collection("organizations").update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) domain = await collections.domains.save(domainOneData) domainTwo = await collections.domains.save(domainTwoData) @@ -738,10 +717,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -751,14 +727,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), }, } @@ -798,10 +768,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -811,14 +778,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), }, } @@ -860,10 +821,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -873,14 +831,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), }, } @@ -920,10 +872,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -933,14 +882,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), }, } @@ -982,10 +925,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -995,14 +935,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), }, } @@ -1042,10 +976,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1055,14 +986,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), }, } @@ -1104,10 +1029,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1117,14 +1039,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), }, } @@ -1165,10 +1081,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1178,14 +1091,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), }, } @@ -1227,10 +1134,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1240,14 +1144,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), }, } @@ -1287,10 +1185,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1300,14 +1195,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), }, } @@ -1349,10 +1238,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1362,14 +1248,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), }, } @@ -1409,10 +1289,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1422,14 +1299,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), }, } @@ -1471,10 +1342,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1484,14 +1352,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), }, } @@ -1531,10 +1393,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1544,14 +1403,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), }, } @@ -1593,10 +1446,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1606,14 +1456,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), }, } @@ -1653,10 +1497,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1666,14 +1507,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), }, } @@ -1715,10 +1550,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1728,14 +1560,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), }, } @@ -1775,10 +1601,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1788,14 +1611,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 +1654,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 +1664,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), }, } @@ -1897,10 +1705,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1910,14 +1715,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), }, } @@ -1960,10 +1759,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1973,14 +1769,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), }, } @@ -2020,10 +1810,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2033,14 +1820,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), }, } @@ -2082,10 +1863,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2095,14 +1873,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), }, } @@ -2143,10 +1915,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2156,14 +1925,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), }, } @@ -2177,16 +1940,16 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { ;({ query, drop } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) afterAll(async () => { @@ -2270,9 +2033,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.'], ]) }) }) @@ -2310,9 +2071,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.'], ]) }) }) @@ -2341,17 +2100,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.'], ]) }) }) @@ -2379,17 +2132,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.'], ]) }) }) @@ -2426,9 +2173,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.'], ]) }) }) @@ -2465,9 +2210,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.'], ]) }) }) @@ -2493,19 +2236,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([ [ @@ -2536,19 +2273,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([ [ @@ -2566,9 +2297,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, @@ -2579,9 +2308,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([ [ @@ -2614,9 +2341,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([ [ @@ -2674,9 +2399,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.'], ]) }) }) @@ -2714,9 +2437,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.'], ]) }) }) @@ -2746,16 +2467,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.'], ]) }) }) @@ -2784,16 +2501,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.'], ]) }) }) @@ -2830,9 +2543,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.'], ]) }) }) @@ -2869,9 +2580,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.'], ]) }) }) @@ -2897,18 +2606,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([ @@ -2940,18 +2645,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([ @@ -2970,9 +2671,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, @@ -2983,9 +2682,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([ [ @@ -3018,9 +2715,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/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js b/api/src/verified-organizations/loaders/__tests__/load-verified-org-conn.test.js index fb795727b1..67ebce6778 100644 --- a/api/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,5 +1,6 @@ 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' @@ -12,16 +13,7 @@ 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, db, org, orgTwo, i18n, domain, domainTwo, domainThree const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -36,33 +28,21 @@ describe('given the load organizations connection function', () => { 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, - })) + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) }) beforeEach(async () => { org = await collections.organizations.save({ verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'slug-org-one', @@ -86,20 +66,15 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgSummary = await collections.organizationSummaries.save({ + organization: org._id, + web: { pass: 50, fail: 1000, total: 1050 }, + mail: { pass: 50, fail: 1000, total: 1050 }, + }) + await db.collection('organizations').update(org._key, { latestSummaryId: orgSummary._id }) + orgTwo = await collections.organizations.save({ verified: true, - summaries: { - web: { - pass: 52, - fail: 1002, - total: 1054, - }, - mail: { - pass: 52, - fail: 1002, - total: 1054, - }, - }, orgDetails: { en: { slug: 'slug-org-two', @@ -123,6 +98,12 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgTwoSummary = await collections.organizationSummaries.save({ + organization: orgTwo._id, + web: { pass: 52, fail: 1002, total: 1054 }, + mail: { pass: 52, fail: 1002, total: 1054 }, + }) + await db.collection('organizations').update(orgTwo._key, { latestSummaryId: orgTwoSummary._id }) domain = await collections.domains.save({ domain: 'test.domain.gc.ca', }) @@ -196,10 +177,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], }, @@ -209,14 +187,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), }, } @@ -249,10 +221,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], }, @@ -262,14 +231,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), }, } @@ -301,10 +264,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], }, @@ -314,14 +274,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), }, } @@ -353,10 +307,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], }, @@ -366,14 +317,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), }, } @@ -385,18 +330,6 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: true, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, orgDetails: { en: { slug: 'slug-org-three', @@ -420,6 +353,12 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + web: { pass: 51, fail: 1001, total: 1052 }, + mail: { pass: 51, fail: 1001, total: 1052 }, + }) + await db.collection('organizations').update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) await collections.claims.save({ _from: orgThree._id, _to: domain._id, @@ -462,10 +401,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -475,14 +411,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), }, } @@ -521,10 +451,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -534,14 +461,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), }, } @@ -582,10 +503,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -595,14 +513,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), }, } @@ -641,10 +553,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -654,14 +563,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), }, } @@ -702,10 +605,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -715,14 +615,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), }, } @@ -761,10 +655,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -774,14 +665,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), }, } @@ -822,10 +707,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -835,14 +717,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), }, } @@ -881,10 +757,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -894,14 +767,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), }, } @@ -942,10 +809,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -955,14 +819,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), }, } @@ -1001,10 +859,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1014,14 +869,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), }, } @@ -1062,10 +911,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1075,14 +921,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), }, } @@ -1121,10 +961,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1134,14 +971,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), }, } @@ -1182,10 +1013,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1195,14 +1023,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), }, } @@ -1241,10 +1063,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1254,14 +1073,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), }, } @@ -1302,10 +1115,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1315,14 +1125,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), }, } @@ -1361,10 +1165,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1374,14 +1175,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), }, } @@ -1422,10 +1217,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1435,14 +1227,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), }, } @@ -1481,10 +1267,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1494,14 +1277,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), }, } @@ -1542,10 +1319,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1555,14 +1329,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), }, } @@ -1601,10 +1369,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1614,14 +1379,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), }, } @@ -1662,10 +1421,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1675,14 +1431,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), }, } @@ -1721,10 +1471,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1734,14 +1481,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), }, } @@ -1782,10 +1523,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1795,14 +1533,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), }, } @@ -1841,10 +1573,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -1854,14 +1583,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), }, } @@ -1944,10 +1667,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], }, @@ -1957,14 +1677,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), }, } @@ -1997,10 +1711,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], }, @@ -2010,14 +1721,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), }, } @@ -2049,10 +1754,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], }, @@ -2062,14 +1764,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), }, } @@ -2101,10 +1797,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], }, @@ -2114,14 +1807,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), }, } @@ -2133,18 +1820,6 @@ describe('given the load organizations connection function', () => { beforeEach(async () => { orgThree = await collections.organizations.save({ verified: true, - summaries: { - web: { - pass: 51, - fail: 1001, - total: 1052, - }, - mail: { - pass: 51, - fail: 1001, - total: 1052, - }, - }, orgDetails: { en: { slug: 'slug-org-three', @@ -2168,6 +1843,12 @@ describe('given the load organizations connection function', () => { }, }, }) + const orgThreeSummary = await collections.organizationSummaries.save({ + organization: orgThree._id, + web: { pass: 51, fail: 1001, total: 1052 }, + mail: { pass: 51, fail: 1001, total: 1052 }, + }) + await db.collection('organizations').update(orgThree._key, { latestSummaryId: orgThreeSummary._id }) await collections.claims.save({ _from: orgThree._id, _to: domain._id, @@ -2210,10 +1891,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2223,14 +1901,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), }, } @@ -2269,10 +1941,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2282,14 +1951,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), }, } @@ -2330,10 +1993,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2343,14 +2003,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), }, } @@ -2389,10 +2043,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2402,14 +2053,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), }, } @@ -2450,10 +2095,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2463,14 +2105,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), }, } @@ -2509,10 +2145,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2522,14 +2155,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), }, } @@ -2570,10 +2197,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2583,14 +2207,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), }, } @@ -2629,10 +2247,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2642,14 +2257,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), }, } @@ -2690,10 +2299,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2703,14 +2309,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), }, } @@ -2749,10 +2349,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2762,14 +2359,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), }, } @@ -2810,10 +2401,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2823,14 +2411,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), }, } @@ -2869,10 +2451,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2882,14 +2461,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), }, } @@ -2930,10 +2503,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -2943,14 +2513,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), }, } @@ -2989,10 +2553,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3002,14 +2563,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), }, } @@ -3050,10 +2605,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3063,14 +2615,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), }, } @@ -3109,10 +2655,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3122,14 +2665,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), }, } @@ -3170,10 +2707,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3183,14 +2717,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), }, } @@ -3229,10 +2757,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3242,14 +2767,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), }, } @@ -3290,10 +2809,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3303,14 +2819,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), }, } @@ -3349,10 +2859,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3362,14 +2869,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), }, } @@ -3410,10 +2911,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3423,14 +2921,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), }, } @@ -3469,10 +2961,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3482,14 +2971,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), }, } @@ -3530,10 +3013,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3543,14 +3023,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), }, } @@ -3589,10 +3063,7 @@ describe('given the load organizations connection function', () => { const expectedStructure = { edges: [ { - cursor: toGlobalId( - 'verifiedOrganization', - expectedOrg._key, - ), + cursor: toGlobalId('verifiedOrganization', expectedOrg._key), node: { ...expectedOrg, }, @@ -3602,14 +3073,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), }, } @@ -3740,9 +3205,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.'), ) } @@ -3769,9 +3232,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.'), ) } @@ -3806,9 +3267,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', () => { @@ -3835,18 +3294,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', @@ -3863,11 +3318,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.`, @@ -3877,9 +3328,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', @@ -3896,11 +3345,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.`, @@ -3913,9 +3358,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, @@ -3930,11 +3373,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([ @@ -3967,11 +3406,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([ @@ -4070,9 +3505,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.'), ) } @@ -4099,9 +3532,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.'), ) } @@ -4136,9 +3567,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', () => { @@ -4165,18 +3594,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', @@ -4194,9 +3619,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([ @@ -4207,9 +3630,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', @@ -4227,9 +3648,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([ @@ -4243,9 +3662,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, @@ -4260,11 +3677,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([ @@ -4298,9 +3711,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/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 index fdd438d603..fd7b4014ad 100644 --- a/api/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,4 +1,5 @@ -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' @@ -9,7 +10,7 @@ import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given a loadVerifiedOrgByKey dataloader', () => { - let query, drop, truncate, collections, i18n + let query, drop, truncate, collections, db, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -22,33 +23,21 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { - await collections.organizations.save({ + const org1 = await collections.organizations.save({ verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'communications-security-establishment', @@ -72,20 +61,22 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { }, }, }) - await collections.organizations.save({ - verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, + const summary1 = await collections.organizationSummaries.save({ + organization: org1._id, + web: { + pass: 50, + fail: 1000, + total: 1050, }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }) + await db.collection("organizations").update(org1._key, { latestSummaryId: summary1._id }) + const org2 = await collections.organizations.save({ + verified: true, orgDetails: { en: { slug: 'treasury-board-secretariat', @@ -109,6 +100,20 @@ describe('given a loadVerifiedOrgByKey dataloader', () => { }, }, }) + const summary2 = await collections.organizationSummaries.save({ + organization: org2._id, + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }) + await db.collection("organizations").update(org2._key, { latestSummaryId: summary2._id }) }) afterEach(async () => { await truncate() @@ -138,7 +143,7 @@ describe('given a loadVerifiedOrgByKey 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("en", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -155,7 +160,7 @@ describe('given a loadVerifiedOrgByKey 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("en", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -192,7 +197,7 @@ describe('given a loadVerifiedOrgByKey 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("fr", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -209,7 +214,7 @@ describe('given a loadVerifiedOrgByKey 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("fr", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -243,9 +248,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', @@ -255,11 +258,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([ @@ -284,11 +283,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([ @@ -314,9 +309,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', @@ -327,9 +320,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.'), ) } @@ -356,9 +347,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/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 index e67fdbe77e..80d3a353f1 100644 --- a/api/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,4 +1,5 @@ -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' @@ -9,7 +10,7 @@ import dbschema from '../../../../database.json' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given a loadVerifiedOrgBySlug dataloader', () => { - let query, drop, truncate, collections, i18n + let query, drop, truncate, collections, db, i18n const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -22,33 +23,21 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { describe('given a successful load', () => { beforeAll(async () => { - ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + ;({ query, drop, truncate, collections, db } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { - await collections.organizations.save({ + const org1 = await collections.organizations.save({ verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, - }, orgDetails: { en: { slug: 'communications-security-establishment', @@ -72,20 +61,22 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { }, }, }) - await collections.organizations.save({ - verified: true, - summaries: { - web: { - pass: 50, - fail: 1000, - total: 1050, - }, - mail: { - pass: 50, - fail: 1000, - total: 1050, - }, + const summary1 = await collections.organizationSummaries.save({ + organization: org1._id, + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, }, + }) + await db.collection("organizations").update(org1._key, { latestSummaryId: summary1._id }) + const org2 = await collections.organizations.save({ + verified: true, orgDetails: { en: { slug: 'treasury-board-secretariat', @@ -109,6 +100,20 @@ describe('given a loadVerifiedOrgBySlug dataloader', () => { }, }, }) + const summary2 = await collections.organizationSummaries.save({ + organization: org2._id, + web: { + pass: 50, + fail: 1000, + total: 1050, + }, + mail: { + pass: 50, + fail: 1000, + total: 1050, + }, + }) + await db.collection("organizations").update(org2._key, { latestSummaryId: summary2._id }) }) afterEach(async () => { await truncate() @@ -138,7 +143,7 @@ describe('given a loadVerifiedOrgBySlug 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("en", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -155,7 +160,7 @@ describe('given a loadVerifiedOrgBySlug 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("en", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("en", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -192,7 +197,7 @@ describe('given a loadVerifiedOrgBySlug 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("fr", org.orgDetails)) ` const expectedOrg = await expectedCursor.next() @@ -209,7 +214,7 @@ describe('given a loadVerifiedOrgBySlug 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, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.summaries }, TRANSLATE("fr", org.orgDetails)) + RETURN MERGE({ _id: org._id, _key: org._key, id: org._key, _rev: org._rev, _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(domains), summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE("fr", org.orgDetails)) ` while (expectedCursor.hasMore) { @@ -243,9 +248,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', @@ -255,11 +258,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([ @@ -284,16 +283,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.`]) }) }) }) @@ -314,9 +307,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', @@ -327,9 +318,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.'), ) } @@ -356,15 +345,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/src/verified-organizations/loaders/load-verified-organization-by-key.js b/api/src/verified-organizations/loaders/load-verified-organization-by-key.js index 225af13261..c8971b6f76 100644 --- a/api/src/verified-organizations/loaders/load-verified-organization-by-key.js +++ b/api/src/verified-organizations/loaders/load-verified-organization-by-key.js @@ -21,8 +21,8 @@ export const loadVerifiedOrgByKey = ({ query, language, i18n }) => _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(orgDomains), - summaries: org.summaries - }, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null + }, TRANSLATE(${language}, org.orgDetails) ) ` diff --git a/api/src/verified-organizations/loaders/load-verified-organization-by-slug.js b/api/src/verified-organizations/loaders/load-verified-organization-by-slug.js index fc889451cf..bcee5f6069 100644 --- a/api/src/verified-organizations/loaders/load-verified-organization-by-slug.js +++ b/api/src/verified-organizations/loaders/load-verified-organization-by-slug.js @@ -21,8 +21,8 @@ export const loadVerifiedOrgBySlug = ({ query, language, i18n }) => _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(orgDomains), - summaries: org.summaries - }, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null + }, TRANSLATE(${language}, org.orgDetails) ) ` diff --git a/api/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 index 7ea69ddce3..239d51374d 100644 --- a/api/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 @@ -50,23 +50,23 @@ export const loadVerifiedOrgConnectionsByDomainId = documentField = aql`afterVar.verified` orgField = aql`org.verified` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`afterVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`afterVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`afterVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`afterVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`afterVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`afterVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` } else if (orderBy.field === 'domain-count') { documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` orgField = aql`COUNT(orgDomains)` @@ -127,23 +127,23 @@ export const loadVerifiedOrgConnectionsByDomainId = documentField = aql`beforeVar.verified` orgField = aql`org.verified` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`beforeVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`beforeVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`beforeVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`beforeVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`beforeVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`beforeVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` } else if (orderBy.field === 'domain-count') { documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` orgField = aql`COUNT(orgDomains)` @@ -266,27 +266,27 @@ export const loadVerifiedOrgConnectionsByDomainId = hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` } else if (orderBy.field === 'summary-mail-pass') { - orgField = aql`org.summaries.mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - orgField = aql`org.summaries.mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` } else if (orderBy.field === 'summary-mail-total') { - orgField = aql`org.summaries.mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` } else if (orderBy.field === 'summary-web-pass') { - orgField = aql`org.summaries.web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` } else if (orderBy.field === 'summary-web-fail') { - orgField = aql`org.summaries.web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` } else if (orderBy.field === 'summary-web-total') { - orgField = aql`org.summaries.web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` } else if (orderBy.field === 'domain-count') { @@ -329,17 +329,17 @@ export const loadVerifiedOrgConnectionsByDomainId = } else if (orderBy.field === 'verified') { sortByField = aql`org.verified ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-pass') { - sortByField = aql`org.summaries.mail.pass ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-fail') { - sortByField = aql`org.summaries.mail.fail ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-total') { - sortByField = aql`org.summaries.mail.total ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.total ${orderBy.direction},` } else if (orderBy.field === 'summary-web-pass') { - sortByField = aql`org.summaries.web.pass ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-web-fail') { - sortByField = aql`org.summaries.web.fail ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-web-total') { - sortByField = aql`org.summaries.web.total ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.total ${orderBy.direction},` } else if (orderBy.field === 'domain-count') { sortByField = aql`COUNT(domains) ${orderBy.direction},` } @@ -381,7 +381,7 @@ export const loadVerifiedOrgConnectionsByDomainId = _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(orgDomains), - summaries: org.summaries + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null }, TRANSLATE(${language}, org.orgDetails) ) diff --git a/api/src/verified-organizations/loaders/load-verified-organizations-connections.js b/api/src/verified-organizations/loaders/load-verified-organizations-connections.js index 2753c388c1..51ea6dc80e 100644 --- a/api/src/verified-organizations/loaders/load-verified-organizations-connections.js +++ b/api/src/verified-organizations/loaders/load-verified-organizations-connections.js @@ -50,23 +50,23 @@ export const loadVerifiedOrgConnections = documentField = aql`afterVar.verified` orgField = aql`org.verified` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`afterVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`afterVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`afterVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`afterVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`afterVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`afterVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + documentField = aql`DOCUMENT(afterVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` } else if (orderBy.field === 'domain-count') { documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` orgField = aql`COUNT(orgDomains)` @@ -125,23 +125,23 @@ export const loadVerifiedOrgConnections = documentField = aql`beforeVar.verified` orgField = aql`org.verified` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`beforeVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`beforeVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`beforeVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`beforeVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`beforeVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`beforeVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + documentField = aql`DOCUMENT(beforeVar.latestSummaryId).web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` } else if (orderBy.field === 'domain-count') { documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` orgField = aql`COUNT(orgDomains)` @@ -264,27 +264,27 @@ export const loadVerifiedOrgConnections = hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` } else if (orderBy.field === 'summary-mail-pass') { - orgField = aql`org.summaries.mail.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.pass` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - orgField = aql`org.summaries.mail.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.fail` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` } else if (orderBy.field === 'summary-mail-total') { - orgField = aql`org.summaries.mail.total` + orgField = aql`DOCUMENT(org.latestSummaryId).mail.total` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` } else if (orderBy.field === 'summary-web-pass') { - orgField = aql`org.summaries.web.pass` + orgField = aql`DOCUMENT(org.latestSummaryId).web.pass` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` } else if (orderBy.field === 'summary-web-fail') { - orgField = aql`org.summaries.web.fail` + orgField = aql`DOCUMENT(org.latestSummaryId).web.fail` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` } else if (orderBy.field === 'summary-web-total') { - orgField = aql`org.summaries.web.total` + orgField = aql`DOCUMENT(org.latestSummaryId).web.total` hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` } else if (orderBy.field === 'domain-count') { @@ -327,17 +327,17 @@ export const loadVerifiedOrgConnections = } else if (orderBy.field === 'verified') { sortByField = aql`org.verified ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-pass') { - sortByField = aql`org.summaries.mail.pass ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-fail') { - sortByField = aql`org.summaries.mail.fail ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-total') { - sortByField = aql`org.summaries.mail.total ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).mail.total ${orderBy.direction},` } else if (orderBy.field === 'summary-web-pass') { - sortByField = aql`org.summaries.web.pass ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-web-fail') { - sortByField = aql`org.summaries.web.fail ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-web-total') { - sortByField = aql`org.summaries.web.total ${orderBy.direction},` + sortByField = aql`DOCUMENT(org.latestSummaryId).web.total ${orderBy.direction},` } else if (orderBy.field === 'domain-count') { sortByField = aql`COUNT(domains) ${orderBy.direction},` } @@ -381,8 +381,8 @@ export const loadVerifiedOrgConnections = _type: "verifiedOrganization", verified: org.verified, domainCount: COUNT(orgDomains), - summaries: org.summaries - }, + summaries: org.latestSummaryId ? DOCUMENT(org.latestSummaryId) : null + }, TRANSLATE(${language}, org.orgDetails) ) ) diff --git a/api/src/verified-organizations/objects/__tests__/verified-organization.test.js b/api/src/verified-organizations/objects/__tests__/verified-organization.test.js index e747a198ea..a626f92b39 100644 --- a/api/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/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 index f94b1bc5af..c994d08ea4 100644 --- a/api/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,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 { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -39,16 +40,16 @@ describe('given findOrganizationBySlugQuery', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { org = await collections.organizations.save({ @@ -105,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" @@ -116,8 +117,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -129,15 +130,14 @@ describe('given findOrganizationBySlugQuery', () => { language: 'en', i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -167,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" @@ -178,8 +178,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: query, validators: { @@ -191,15 +191,14 @@ describe('given findOrganizationBySlugQuery', () => { language: 'fr', i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -232,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" @@ -243,8 +242,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), validators: { @@ -256,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) }) @@ -285,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" @@ -296,8 +291,8 @@ describe('given findOrganizationBySlugQuery', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, query: jest.fn(), validators: { @@ -309,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/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js b/api/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js index 9d9f4f08cf..2b212ac3d8 100644 --- a/api/src/verified-organizations/queries/__tests__/find-verified-organizations.test.js +++ b/api/src/verified-organizations/queries/__tests__/find-verified-organizations.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 } from 'graphql' import { toGlobalId } from 'graphql-relay' import { setupI18n } from '@lingui/core' @@ -39,16 +40,16 @@ describe('given findVerifiedOrganizations', () => { beforeAll(async () => { // Generate DB Items ;({ query, drop, truncate, collections } = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) }) beforeEach(async () => { orgOne = await collections.organizations.save({ @@ -125,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 { @@ -146,8 +147,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -156,15 +157,14 @@ describe('given findVerifiedOrganizations', () => { cleanseInput, i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -199,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 { @@ -220,8 +220,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -230,15 +230,14 @@ describe('given findVerifiedOrganizations', () => { cleanseInput, i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -275,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 { @@ -296,8 +295,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -306,15 +305,14 @@ describe('given findVerifiedOrganizations', () => { cleanseInput, i18n, }), - loadVerifiedDomainConnectionsByOrgId: - loadVerifiedDomainConnectionsByOrgId({ - query, - cleanseInput, - i18n, - }), + loadVerifiedDomainConnectionsByOrgId: loadVerifiedDomainConnectionsByOrgId({ + query, + cleanseInput, + i18n, + }), }, }, - ) + }) const expectedResponse = { data: { @@ -349,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 { @@ -370,8 +368,8 @@ describe('given findVerifiedOrganizations', () => { } } `, - null, - { + rootValue: null, + contextValue: { i18n, loaders: { loadVerifiedOrgConnections: loadVerifiedOrgConnections({ @@ -380,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 index 04fddad420..e45d1ab8a9 100644 --- a/api/src/verified-organizations/queries/find-verified-organization-by-slug.js +++ b/api/src/verified-organizations/queries/find-verified-organization-by-slug.js @@ -8,16 +8,11 @@ export const findVerifiedOrganizationBySlug = { 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.', + type: new GraphQLNonNull(Slug), + description: 'The slugified organization name you want to retrieve data for.', }, }, - resolve: async ( - _, - args, - { i18n, loaders: { loadVerifiedOrgBySlug }, validators: { cleanseInput } }, - ) => { + resolve: async (_, args, { i18n, loaders: { loadVerifiedOrgBySlug }, validators: { cleanseInput } }) => { // Cleanse input const orgSlug = cleanseInput(args.orgSlug) @@ -26,11 +21,7 @@ export const findVerifiedOrganizationBySlug = { 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.`, - ), - ) + throw new Error(i18n._(t`No verified organization with the provided slug could be found.`)) } return org 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 index 20b59a8da4..d1175cffd4 100644 --- a/api/src/web-scan/index.js +++ b/api/src/web-scan/index.js @@ -1,3 +1,3 @@ export * from './loaders' export * from './objects' -export * from './subscriptions' +export * from './data-source' diff --git a/api/src/web-scan/inputs/__tests__/https-order.test.js b/api/src/web-scan/inputs/__tests__/https-order.test.js deleted file mode 100644 index b2bf051d78..0000000000 --- a/api/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/src/web-scan/inputs/__tests__/ssl-order.test.js b/api/src/web-scan/inputs/__tests__/ssl-order.test.js deleted file mode 100644 index 9085ff95fd..0000000000 --- a/api/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/src/web-scan/inputs/https-order.js b/api/src/web-scan/inputs/https-order.js deleted file mode 100644 index 706d82eb93..0000000000 --- a/api/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/src/web-scan/inputs/index.js b/api/src/web-scan/inputs/index.js index 24e44e80ff..ae86edc1a7 100644 --- a/api/src/web-scan/inputs/index.js +++ b/api/src/web-scan/inputs/index.js @@ -1,2 +1 @@ -export * from './https-order' -export * from './ssl-order' +export * from './web-order' diff --git a/api/src/web-scan/inputs/ssl-order.js b/api/src/web-scan/inputs/ssl-order.js deleted file mode 100644 index d2d489f5ad..0000000000 --- a/api/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/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/__tests__/load-https-by-key.test.js b/api/src/web-scan/loaders/__tests__/load-https-by-key.test.js deleted file mode 100644 index 4f1c40c8df..0000000000 --- a/api/src/web-scan/loaders/__tests__/load-https-by-key.test.js +++ /dev/null @@ -1,218 +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 { loadHttpsByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/web-scan/loaders/__tests__/load-https-connections-by-domain-id.test.js b/api/src/web-scan/loaders/__tests__/load-https-connections-by-domain-id.test.js deleted file mode 100644 index 1459d7160f..0000000000 --- a/api/src/web-scan/loaders/__tests__/load-https-connections-by-domain-id.test.js +++ /dev/null @@ -1,1832 +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 { cleanseInput } from '../../../validators' -import { loadHttpsConnectionsByDomainId, loadHttpsByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - 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/src/web-scan/loaders/__tests__/load-ssl-by-key.test.js b/api/src/web-scan/loaders/__tests__/load-ssl-by-key.test.js deleted file mode 100644 index 983c1ce04b..0000000000 --- a/api/src/web-scan/loaders/__tests__/load-ssl-by-key.test.js +++ /dev/null @@ -1,218 +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 { loadSslByKey } from '../index' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/web-scan/loaders/__tests__/load-ssl-connections-by-domain-id.test.js b/api/src/web-scan/loaders/__tests__/load-ssl-connections-by-domain-id.test.js deleted file mode 100644 index abe7a6b13d..0000000000 --- a/api/src/web-scan/loaders/__tests__/load-ssl-connections-by-domain-id.test.js +++ /dev/null @@ -1,2229 +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 { cleanseInput } from '../../../validators' -import { loadSslByKey, loadSslConnectionByDomainId } from '../index' -import dbschema from '../../../../database.json' - -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({ - 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, - }) - 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/src/web-scan/loaders/index.js b/api/src/web-scan/loaders/index.js index badd1c0d59..e8e9e78b37 100644 --- a/api/src/web-scan/loaders/index.js +++ b/api/src/web-scan/loaders/index.js @@ -1,4 +1,2 @@ -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' +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-https-by-key.js b/api/src/web-scan/loaders/load-https-by-key.js deleted file mode 100644 index eda0c92941..0000000000 --- a/api/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/src/web-scan/loaders/load-https-connections-by-domain-id.js b/api/src/web-scan/loaders/load-https-connections-by-domain-id.js deleted file mode 100644 index a368721e18..0000000000 --- a/api/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/src/web-scan/loaders/load-ssl-by-key.js b/api/src/web-scan/loaders/load-ssl-by-key.js deleted file mode 100644 index 99e4197fa0..0000000000 --- a/api/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/src/web-scan/loaders/load-ssl-connections-by-domain-id.js b/api/src/web-scan/loaders/load-ssl-connections-by-domain-id.js deleted file mode 100644 index ae88fc8185..0000000000 --- a/api/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/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/__tests__/https-connection.test.js b/api/src/web-scan/objects/__tests__/https-connection.test.js deleted file mode 100644 index 3bf41b68c3..0000000000 --- a/api/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/src/web-scan/objects/__tests__/https-sub.test.js b/api/src/web-scan/objects/__tests__/https-sub.test.js deleted file mode 100644 index 031fab8f4a..0000000000 --- a/api/src/web-scan/objects/__tests__/https-sub.test.js +++ /dev/null @@ -1,445 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLString, GraphQLList, GraphQLID } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -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' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/web-scan/objects/__tests__/https.test.js b/api/src/web-scan/objects/__tests__/https.test.js deleted file mode 100644 index 9fa1cbc3d2..0000000000 --- a/api/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/src/web-scan/objects/__tests__/ssl-connection.test.js b/api/src/web-scan/objects/__tests__/ssl-connection.test.js deleted file mode 100644 index f5c42fa624..0000000000 --- a/api/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/src/web-scan/objects/__tests__/ssl-sub.test.js b/api/src/web-scan/objects/__tests__/ssl-sub.test.js deleted file mode 100644 index eea3f8c0a8..0000000000 --- a/api/src/web-scan/objects/__tests__/ssl-sub.test.js +++ /dev/null @@ -1,555 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { GraphQLBoolean, GraphQLID, GraphQLList, GraphQLString } from 'graphql' -import { GraphQLJSON } from 'graphql-scalars' - -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' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - 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/src/web-scan/objects/__tests__/ssl.test.js b/api/src/web-scan/objects/__tests__/ssl.test.js deleted file mode 100644 index 225935bfac..0000000000 --- a/api/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/src/web-scan/objects/__tests__/web-scan.test.js b/api/src/web-scan/objects/__tests__/web-scan.test.js deleted file mode 100644 index 7af7cd782e..0000000000 --- a/api/src/web-scan/objects/__tests__/web-scan.test.js +++ /dev/null @@ -1,213 +0,0 @@ -import { ensure, dbNameFromFile } from 'arango-tools' -import { toGlobalId } from 'graphql-relay' - -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' - -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - }) - - 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/src/web-scan/objects/https-connection.js b/api/src/web-scan/objects/https-connection.js deleted file mode 100644 index 0e8ae14ff1..0000000000 --- a/api/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/src/web-scan/objects/https-sub.js b/api/src/web-scan/objects/https-sub.js deleted file mode 100644 index fd8e1b3c41..0000000000 --- a/api/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/src/web-scan/objects/https.js b/api/src/web-scan/objects/https.js deleted file mode 100644 index bfa770f7af..0000000000 --- a/api/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/src/web-scan/objects/index.js b/api/src/web-scan/objects/index.js index 2653cead0d..679466684e 100644 --- a/api/src/web-scan/objects/index.js +++ b/api/src/web-scan/objects/index.js @@ -1,7 +1,6 @@ -export * from './https-sub' -export * from './https' -export * from './https-connection' -export * from './ssl-sub' -export * from './ssl' -export * from './ssl-connection' +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/ssl-connection.js b/api/src/web-scan/objects/ssl-connection.js deleted file mode 100644 index a6f5580185..0000000000 --- a/api/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/src/web-scan/objects/ssl-sub.js b/api/src/web-scan/objects/ssl-sub.js deleted file mode 100644 index b2640d1ceb..0000000000 --- a/api/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/src/web-scan/objects/ssl.js b/api/src/web-scan/objects/ssl.js deleted file mode 100644 index a18b2901df..0000000000 --- a/api/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/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 index 1fb05945b5..8bf70f1245 100644 --- a/api/src/web-scan/objects/web-scan.js +++ b/api/src/web-scan/objects/web-scan.js @@ -1,84 +1,27 @@ -import { GraphQLObjectType } from 'graphql' -import { connectionArgs } from 'graphql-relay' -import { GraphQLDate } from 'graphql-scalars' +import { GraphQLBoolean, GraphQLObjectType, GraphQLString } from 'graphql' -import { domainType } from '../../domain/objects' -import { httpsOrder, sslOrder } from '../inputs' -import { httpsConnection } from './https-connection' -import { sslConnection } from './ssl-connection' +import { webScanResultType } from './web-scan-result' 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 - }, + ipAddress: { + type: GraphQLString, + description: `IP address for scan target.`, }, - 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 - }, + status: { + type: GraphQLString, + description: `The status of the scan for the given domain and IP address.`, }, - 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 - }, + 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: `Results of HTTPS, and SSL scan on the given domain.`, + 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/src/web-scan/subscriptions/__tests__/https-scan-data.test.js b/api/src/web-scan/subscriptions/__tests__/https-scan-data.test.js deleted file mode 100644 index 902eb26945..0000000000 --- a/api/src/web-scan/subscriptions/__tests__/https-scan-data.test.js +++ /dev/null @@ -1,346 +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 { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadHttpsGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - - 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/src/web-scan/subscriptions/__tests__/ssl-scan-data.test.js b/api/src/web-scan/subscriptions/__tests__/ssl-scan-data.test.js deleted file mode 100644 index dcf43cdd78..0000000000 --- a/api/src/web-scan/subscriptions/__tests__/ssl-scan-data.test.js +++ /dev/null @@ -1,375 +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 { createQuerySchema } from '../../../query' -import { createSubscriptionSchema } from '../../../subscription' -import { loadSslGuidanceTagByTagId } from '../../../guidance-tag/loaders' -import { loadDomainByKey } from '../../../domain/loaders' -import dbschema from '../../../../database.json' - -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({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, - - schema: dbschema, - })) - - 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/src/web-scan/subscriptions/https-scan-data.js b/api/src/web-scan/subscriptions/https-scan-data.js deleted file mode 100644 index d579c60f8c..0000000000 --- a/api/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/src/web-scan/subscriptions/index.js b/api/src/web-scan/subscriptions/index.js deleted file mode 100644 index 512218607a..0000000000 --- a/api/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/src/web-scan/subscriptions/ssl-scan-data.js b/api/src/web-scan/subscriptions/ssl-scan-data.js deleted file mode 100644 index a8fe970dd6..0000000000 --- a/api/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/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 aa6fa985b3..0000000000 --- a/app/aks/arangodb-deployment.yaml +++ /dev/null @@ -1,12 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain - diff --git a/app/aks/backup-cronjob.yaml b/app/aks/backup-cronjob.yaml deleted file mode 100644 index 66f75dc2e2..0000000000 --- a/app/aks/backup-cronjob.yaml +++ /dev/null @@ -1,67 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - labels: - job: backup - name: backup - namespace: db -spec: - concurrencyPolicy: Replace - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - args: - - rclone copy /tmp/dump :azureblob:dbdump/tracker-backup-$(date -Iminutes) - command: - - /bin/sh - - -c - env: - - name: RCLONE_AZUREBLOB_ACCOUNT - valueFrom: - secretKeyRef: - key: az-dbdump-account - name: az-dbdump-account - - name: RCLONE_AZUREBLOB_KEY - valueFrom: - secretKeyRef: - key: az-dbdump-sak - name: az-dbdump-sak - volumeMounts: - - mountPath: /tmp/dump - name: dump - image: rclone/rclone - name: upload - initContainers: - - args: - - arangodump - - --server.endpoint=tcp://arangodb:8529 - - --server.username=$(DB_USER) - - --server.password=$(DB_PASS) - - --server.database=track_dmarc - - --output-directory=/tmp/dump - env: - - name: DB_USER - valueFrom: - secretKeyRef: - key: username - name: arangodb - - name: DB_PASS - valueFrom: - secretKeyRef: - key: password - name: arangodb - image: arangodb:3.7.11 - name: dump - volumeMounts: - - mountPath: /tmp/dump - name: dump - restartPolicy: Never - volumes: - - emptyDir: {} - name: dump - schedule: 59 3 * * * - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 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 68fc25a60c..0000000000 --- a/app/aks/kustomization.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- storage-classes.yaml -# - backup-cronjob.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 6681d3528c..0000000000 --- a/app/aks/publicgateway.yaml +++ /dev/null @@ -1,36 +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_ECDHE_RSA_WITH_AES_128_GCM_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/README.md b/app/arangodb/README.md deleted file mode 100644 index c3ca56c107..0000000000 --- a/app/arangodb/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# ArangoDB - -This is just to make working with Kube-ArangoDB a little easier. Upgrading the operator basically just looks like incrementing the version numbers in the kustomization file and then running the following command: - -``` -kustomize build app/arangodb/ > app/bases/arangodb-operator.yaml -``` 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 06e5a1d5d0..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.2.6/manifests/arango-crd.yaml -- https://raw.githubusercontent.com/arangodb/kube-arangodb/1.2.6/manifests/arango-deployment.yaml -- https://raw.githubusercontent.com/arangodb/kube-arangodb/1.2.6/manifests/arango-storage.yaml -- https://raw.githubusercontent.com/arangodb/kube-arangodb/1.2.6/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 2937446d34..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.12 - 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 db9800e550..0000000000 --- a/app/bases/arangodb-operator.yaml +++ /dev/null @@ -1,1236 +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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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/v1 -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.2.6 - 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 - versions: - - name: v1alpha - served: true - storage: false - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - - name: v1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true ---- -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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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/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/dmarc-report-cronjob.yaml b/app/bases/dmarc-report-cronjob.yaml deleted file mode 100644 index d7c246ba4c..0000000000 --- a/app/bases/dmarc-report-cronjob.yaml +++ /dev/null @@ -1,78 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: dmarc-report - namespace: scanners -spec: - schedule: "30 10 * * *" - concurrencyPolicy: Replace - startingDeadlineSeconds: 180 - jobTemplate: - spec: - template: - spec: - containers: - - name: dmarc-report - image: gcr.io/track-compliance/dmarc-report:master-e1d6414-1645125832 # {"$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/guidance-cronjob.yaml b/app/bases/guidance-cronjob.yaml deleted file mode 100755 index 67c088b755..0000000000 --- a/app/bases/guidance-cronjob.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: guidance - namespace: scanners -spec: - schedule: "59 22 * * *" - concurrencyPolicy: Replace - startingDeadlineSeconds: 180 - jobTemplate: - spec: - template: - spec: - containers: - - name: guidance - image: gcr.io/track-compliance/services/guidance:master-3550bbd-1636495510 # {"$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/kustomization.yaml b/app/bases/kustomization.yaml deleted file mode 100644 index 8ed70cbe0e..0000000000 --- a/app/bases/kustomization.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- 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 -- guidance-cronjob.yaml -- summaries-cronjob.yaml -- dmarc-report-cronjob.yaml -- scan-dispatcher-cronjob.yaml diff --git a/app/bases/log4shell-processor-service.yaml b/app/bases/log4shell-processor-service.yaml deleted file mode 100644 index 993059ec6a..0000000000 --- a/app/bases/log4shell-processor-service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: log4shell-processor - name: log4shell-processor - namespace: scanners -spec: - loadBalancerIP: "34.95.53.80" - ports: - - name: udp-53 - protocol: UDP - port: 53 - targetPort: 5353 - selector: - app: log4shell-processor - type: LoadBalancer -status: - loadBalancer: {} diff --git a/app/bases/publicgateway.yaml b/app/bases/publicgateway.yaml deleted file mode 100644 index 1de3cdb043..0000000000 --- a/app/bases/publicgateway.yaml +++ /dev/null @@ -1,29 +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 diff --git a/app/bases/scan-dispatcher-cronjob.yaml b/app/bases/scan-dispatcher-cronjob.yaml deleted file mode 100644 index a7d6db7f2f..0000000000 --- a/app/bases/scan-dispatcher-cronjob.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: scan-dispatcher - namespace: scanners -spec: - schedule: "0 0 * * *" - jobTemplate: - spec: - template: - metadata: - labels: - app: domain-dispatcher - spec: - containers: - - name: domain-dispatcher - image: gcr.io/track-compliance/domain-dispatcher:test-asdfghj-1632166692 - env: - - name: DB_PASS - valueFrom: - secretKeyRef: - name: scanners - key: DB_PASS - - name: DB_USER - valueFrom: - secretKeyRef: - name: scanners - key: DB_USER - - name: DB_HOST - value: arangodb.db - - name: DB_PORT - value: "8529" - - name: DB_NAME - value: track_dmarc - - name: DB_COLLECTION - value: domains - - name: PUBLISH_TO - value: domains - - name: NATS_URL - value: nats://nats.pubsub:4222 - restartPolicy: Never - backoffLimit: 4 diff --git a/app/bases/summaries-cronjob.yaml b/app/bases/summaries-cronjob.yaml deleted file mode 100644 index 957eea1ab9..0000000000 --- a/app/bases/summaries-cronjob.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: summaries - namespace: scanners -spec: - schedule: "30 10 * * *" - concurrencyPolicy: Replace - startingDeadlineSeconds: 180 - jobTemplate: - spec: - template: - spec: - containers: - - name: summaries - image: gcr.io/track-compliance/services/summaries:master-5a56aae-1644958280 # {"$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/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml deleted file mode 100644 index 3267428154..0000000000 --- a/app/bases/tracker-api-deployment.yaml +++ /dev/null @@ -1,259 +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-b8c5af5-1646955725 # {"$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: LOGIN_REQUIRED - valueFrom: - secretKeyRef: - name: api - key: LOGIN_REQUIRED - - 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 - - name: HASHING_SALT - valueFrom: - secretKeyRef: - name: api - key: HASHING_SALT - 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 4877569f14..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-97631c2-1647629715 # {"$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 2ca83ad5cd..0000000000 --- a/app/gke/arangodb-deployment.yaml +++ /dev/null @@ -1,11 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain diff --git a/app/gke/backup-cronjob.yaml b/app/gke/backup-cronjob.yaml deleted file mode 100644 index 25adfba158..0000000000 --- a/app/gke/backup-cronjob.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - labels: - job: backup - name: backup - namespace: db -spec: - concurrencyPolicy: Replace - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - args: - - rclone copy /tmp/dump :gcs:gc-tracker-backups/tracker-backup-$(date - -Iminutes) - command: - - /bin/sh - - -c - env: - - name: RCLONE_GCS_PROJECT_NUMBER - value: '958151870606' - - name: RCLONE_GCS_BUCKET_POLICY_ONLY - value: 'true' - image: rclone/rclone - name: upload - volumeMounts: - - mountPath: /tmp/dump - name: dump - initContainers: - - args: - - arangodump - - --server.endpoint=tcp://arangodb:8529 - - --server.username=$(DB_USER) - - --server.password=$(DB_PASS) - - --server.database=track_dmarc - - --output-directory=/tmp/dump - env: - - name: DB_USER - valueFrom: - secretKeyRef: - key: username - name: arangodb - - name: DB_PASS - valueFrom: - secretKeyRef: - key: password - name: arangodb - image: arangodb:3.7.11 - name: dump - volumeMounts: - - mountPath: /tmp/dump - name: dump - restartPolicy: Never - serviceAccountName: backup-service - volumes: - - emptyDir: {} - name: dump - schedule: 59 3 * * * - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 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 94be640aa8..0000000000 --- a/app/gke/kustomization.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../bases -- storage-classes.yaml -- backup-service-account.yaml -- backup-cronjob.yaml -patchesStrategicMerge: -- arangodb-deployment.yaml -- tracker-api-deployment.yaml -- tracker-frontend-deployment.yaml -- publicgateway.yaml -- certificate.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/gke/log4shell-processor-deployment.yaml b/app/gke/log4shell-processor-deployment.yaml deleted file mode 100644 index b967f90f25..0000000000 --- a/app/gke/log4shell-processor-deployment.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: log4shell-processor - name: log4shell-processor - namespace: scanners -spec: - replicas: 1 - selector: - matchLabels: - app: log4shell-processor - strategy: {} - template: - metadata: - labels: - app: log4shell-processor - spec: - containers: - - name: log4shell-processor - image: gcr.io/track-compliance/log4shell-processor:master-4a2a98d-1639670130 # {"$imagepolicy": "flux-system:log4shell-processor"} - resources: {} -status: {} diff --git a/app/gke/log4shell-scanner-deployment.yaml b/app/gke/log4shell-scanner-deployment.yaml deleted file mode 100644 index 489b77d9de..0000000000 --- a/app/gke/log4shell-scanner-deployment.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: log4shell-scanner - name: log4shell-scanner - namespace: scanners -spec: - selector: - matchLabels: - app: log4shell-scanner - strategy: {} - template: - metadata: - labels: - app: log4shell-scanner - spec: - containers: - - name: log4shell-scanner - image: gcr.io/track-compliance/log4shell-scanner:master-c3f48cc-1640366617 # {"$imagepolicy": "flux-system:log4shell-scanner"} - env: - - name: PYTHONWARNINGS - value: ignore - - name: PUBLISH_TO - value: domains - - name: SUBSCRIBE_TO - value: domains.* - - name: QUEUE_GROUP - value: log4shellscanner - - name: NATS_SERVERS - value: nats://nats.pubsub:4222 - - name: TIMEOUT - value: "4" - - name: NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - imagePullPolicy: Always - resources: {} -status: {} diff --git a/app/gke/publicgateway.yaml b/app/gke/publicgateway.yaml deleted file mode 100644 index 993396e027..0000000000 --- a/app/gke/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: - - "*" - 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 - - ECDHE-ECDSA-AES256-GCM-SHA384 - - ECDHE-RSA-AES256-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/scan-job.yaml b/app/jobs/scan-job.yaml deleted file mode 100644 index c50a97224f..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/super-admin.yaml b/app/jobs/super-admin.yaml deleted file mode 100644 index c8b277c53a..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-21b0f7a-1643820832 # {"$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 aa6fa985b3..0000000000 --- a/app/production/arangodb-deployment.yaml +++ /dev/null @@ -1,12 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain - diff --git a/app/production/backup-cronjob.yaml b/app/production/backup-cronjob.yaml deleted file mode 100644 index 66f75dc2e2..0000000000 --- a/app/production/backup-cronjob.yaml +++ /dev/null @@ -1,67 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - labels: - job: backup - name: backup - namespace: db -spec: - concurrencyPolicy: Replace - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - args: - - rclone copy /tmp/dump :azureblob:dbdump/tracker-backup-$(date -Iminutes) - command: - - /bin/sh - - -c - env: - - name: RCLONE_AZUREBLOB_ACCOUNT - valueFrom: - secretKeyRef: - key: az-dbdump-account - name: az-dbdump-account - - name: RCLONE_AZUREBLOB_KEY - valueFrom: - secretKeyRef: - key: az-dbdump-sak - name: az-dbdump-sak - volumeMounts: - - mountPath: /tmp/dump - name: dump - image: rclone/rclone - name: upload - initContainers: - - args: - - arangodump - - --server.endpoint=tcp://arangodb:8529 - - --server.username=$(DB_USER) - - --server.password=$(DB_PASS) - - --server.database=track_dmarc - - --output-directory=/tmp/dump - env: - - name: DB_USER - valueFrom: - secretKeyRef: - key: username - name: arangodb - - name: DB_PASS - valueFrom: - secretKeyRef: - key: password - name: arangodb - image: arangodb:3.7.11 - name: dump - volumeMounts: - - mountPath: /tmp/dump - name: dump - restartPolicy: Never - volumes: - - emptyDir: {} - name: dump - schedule: 59 3 * * * - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 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/kustomization.yaml b/app/production/kustomization.yaml deleted file mode 100644 index 98d406f0cc..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 -- backup-cronjob.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/production/publicgateway.yaml b/app/production/publicgateway.yaml deleted file mode 100644 index 9453055785..0000000000 --- a/app/production/publicgateway.yaml +++ /dev/null @@ -1,36 +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_ECDHE_RSA_WITH_AES_128_GCM_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 aa6fa985b3..0000000000 --- a/app/staging/arangodb-deployment.yaml +++ /dev/null @@ -1,12 +0,0 @@ -kind: ArangoDeployment -apiVersion: database.arangodb.com/v1alpha -metadata: - name: arangodb - namespace: db -spec: - dbservers: - volumeClaimTemplate: - spec: - storageClassName: fast-retain - persistentVolumeReclaimPolicy: Retain - diff --git a/app/staging/backup-cronjob.yaml b/app/staging/backup-cronjob.yaml deleted file mode 100644 index 66f75dc2e2..0000000000 --- a/app/staging/backup-cronjob.yaml +++ /dev/null @@ -1,67 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - labels: - job: backup - name: backup - namespace: db -spec: - concurrencyPolicy: Replace - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - args: - - rclone copy /tmp/dump :azureblob:dbdump/tracker-backup-$(date -Iminutes) - command: - - /bin/sh - - -c - env: - - name: RCLONE_AZUREBLOB_ACCOUNT - valueFrom: - secretKeyRef: - key: az-dbdump-account - name: az-dbdump-account - - name: RCLONE_AZUREBLOB_KEY - valueFrom: - secretKeyRef: - key: az-dbdump-sak - name: az-dbdump-sak - volumeMounts: - - mountPath: /tmp/dump - name: dump - image: rclone/rclone - name: upload - initContainers: - - args: - - arangodump - - --server.endpoint=tcp://arangodb:8529 - - --server.username=$(DB_USER) - - --server.password=$(DB_PASS) - - --server.database=track_dmarc - - --output-directory=/tmp/dump - env: - - name: DB_USER - valueFrom: - secretKeyRef: - key: username - name: arangodb - - name: DB_PASS - valueFrom: - secretKeyRef: - key: password - name: arangodb - image: arangodb:3.7.11 - name: dump - volumeMounts: - - mountPath: /tmp/dump - name: dump - restartPolicy: Never - volumes: - - emptyDir: {} - name: dump - schedule: 59 3 * * * - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 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/kustomization.yaml b/app/staging/kustomization.yaml deleted file mode 100644 index 98d406f0cc..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 -- backup-cronjob.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/staging/publicgateway.yaml b/app/staging/publicgateway.yaml deleted file mode 100644 index 9453055785..0000000000 --- a/app/staging/publicgateway.yaml +++ /dev/null @@ -1,36 +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_ECDHE_RSA_WITH_AES_128_GCM_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/backup-cronjob.yaml b/app/test/backup-cronjob.yaml deleted file mode 100644 index e86cb5ba37..0000000000 --- a/app/test/backup-cronjob.yaml +++ /dev/null @@ -1,64 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - labels: - job: backup - name: backup - namespace: db -spec: - concurrencyPolicy: Replace - failedJobsHistoryLimit: 0 - jobTemplate: - spec: - template: - spec: - containers: - - args: - - rclone copy /tmp/dump :gcs:gc-tracker-backups/tracker-test-backup-$(date - -Iminutes) - command: - - /bin/sh - - -c - env: - - name: RCLONE_GCS_PROJECT_NUMBER - value: '958151870606' - - name: RCLONE_GCS_BUCKET_POLICY_ONLY - value: 'true' - image: rclone/rclone - name: upload - volumeMounts: - - mountPath: /tmp/dump - name: dump - initContainers: - - args: - - arangodump - - --server.endpoint=tcp://arangodb:8529 - - --server.username=$(DB_USER) - - --server.password=$(DB_PASS) - - --server.database=track_dmarc - - --output-directory=/tmp/dump - env: - - name: DB_USER - valueFrom: - secretKeyRef: - key: username - name: arangodb - - name: DB_PASS - valueFrom: - secretKeyRef: - key: password - name: arangodb - image: arangodb:3.7.11 - name: dump - volumeMounts: - - mountPath: /tmp/dump - name: dump - restartPolicy: Never - serviceAccountName: backup-service - volumes: - - emptyDir: {} - name: dump - schedule: 59 3 * * * - startingDeadlineSeconds: 180 - successfulJobsHistoryLimit: 0 - diff --git a/app/test/kustomization.yaml b/app/test/kustomization.yaml deleted file mode 100644 index 941c3f3f6f..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 -- backup-service-account.yaml -- backup-cronjob.yaml -patchesStrategicMerge: -- 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/test/publicgateway.yaml b/app/test/publicgateway.yaml deleted file mode 100644 index 6681d3528c..0000000000 --- a/app/test/publicgateway.yaml +++ /dev/null @@ -1,36 +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_ECDHE_RSA_WITH_AES_128_GCM_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..bc781deaa5 --- /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.2.2 +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..1508ec1773 --- /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.2.2 +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/azure-defender-easm/import-easm-additional-findings/clients/__init__.py b/azure-defender-easm/import-easm-additional-findings/clients/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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..bf3821bcd8 --- /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.2.2 +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/azure-defender-easm/label-known-easm-assets/clients/__init__.py b/azure-defender-easm/label-known-easm-assets/clients/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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..d9cf725d4c --- /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.2.2 +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 111673d8a4..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 >= '3.7'", + "version": "==24.2.0" + }, + "backoff": { + "hashes": [ + "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", + "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8" + ], + "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" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" + "index": "pypi", + "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==44.0.1" }, - "chardet": { + "exceptiongroup": { "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", + "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" ], - "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.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:c78d09596d347e1cffd266c5384abfedf43ed1eae08729773bebb3d527fe5a14", - "sha256:e65975b6a13878f9113a1fa5320760585b522d139944e005936b1b8358d0651a" + "sha256:9d72ed2c4ac93682fe55a0a2939548c1b3d23bd7e2ad76f7f8faef4d99d606ec", + "sha256:f3a8ab44a436651608b8e0335800b175c4bb3a7bf0217bbc97babf6d9517218a" ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==3.1.6" + "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,731 +622,950 @@ }, "typing-extensions": { "hashes": [ - "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", - "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", - "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + "sha256:be199d06d8f09ca2c9425e3aa04a9afba33e892fe079dea959e72df7f8442343", + "sha256:f933a7b288a919ca97adbff656e52ff81f7ff25d98a2aabb9355ca4090f772fe" ], - "version": "==3.10.0.2" + "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:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da", - "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994" - ], - "markers": "python_version >= '3.6'", - "version": "==4.1.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:016ba30a6c3e93e48fd57b1a8624516aeba61f85ed1d5c31d9dd34f77fdff4f6", - "sha256:0e4d1c32812d5885956ba1489052ecd86a8cdde2f334568343e8bc646c7e2cb4", - "sha256:104516acf2608c22b5d44a58e0819da275c21e1751df69bd077a70fc8f016e18", - "sha256:1edddb00facd25f2fb0cc2f271b3a795cbbe804b36319ace01d15fb962309293", - "sha256:23feec3ff73fd47d9bf3fed96e5debb32727c67c9dcbfea67e172f8fac4500e6", - "sha256:28036db6711fcc83d154e3022270508bcfe20a4839e79b8f9f0776555512f2a4", - "sha256:2941c0ae3161b185b116eb939386c9c1dc8d4294ea0a3886c7248c50c23defe8", - "sha256:2bcca3d8cbaf4e3250f5c4916bf32e2da8ce3fd8163303ce41d9b703df38d31e", - "sha256:3d804d22084ba6cee9922cab3b0f665021be93efe40d528c6c32a73caae587eb", - "sha256:3e11347d5d19bd13359587270f2b4580fc3a6761fedb7d4a81ffac2e68c66b6c", - "sha256:415529aaf4d9b4c00b3a75fb30f82cafbb4487f96b3c7fbf424cebc978c96a32", - "sha256:42e4cb8e0705f7757816bfa6a771db1c5933f69cbab0d96a1a5a2d955237086e", - "sha256:577c7c7a5f7065ec9bf33b9ca2de96d8f579e39fff3bc94a9acd018c0db7dac1", - "sha256:66dee5853862e63d521354f7034714ad1c925e025641a4a19548e32580790a47", - "sha256:6a78361a30688afc1c79f09cee629dc5f6d627f7fbd43ab9a37b239e7609940f", - "sha256:6de0c4a6e53877696115221aefbea22eb9d31a4b2fc0756c7e4c970668376a60", - "sha256:6e1db0b63067f1a96f1b92fc8149aa20327a11d18ab3e3ad23be2d8aeb3a6bbb", - "sha256:71e842ed506fd588a427cbc127dc91d0727468f73d581fed02955256cd4bbdbb", - "sha256:720985a41a7be5ca5d17fa9196b4965c226b85f6c57e7b2f6c6debed657faa23", - "sha256:7623bbfc421d26698cc9dc4b8907ca6a149c7547d5c94ac6f55b1685e9f187b1", - "sha256:78f691b8c882d65162062bb10a69423f9d3005b3c8ec9977c60e14fe0036525f", - "sha256:8a2b1001ece83aabd818d334659a97cf3c0158501442f20fee7305c8ac3059cb", - "sha256:8c16099cb2d5a61b9ac4aa77de25a6376bb96c8f94ea96e2c69e625cd222e7ae", - "sha256:94261a9a318c6620b1e079f2ce15ff13c90305e880eaba1f9450e47ac4ea8b4a", - "sha256:9660de460a5cae80b896e560dcdbf4a4f1d055a88512207d582a361f47eb0dca", - "sha256:966df3527b71319f28fc6a723b727d5d6abc3298dff56262b20cc4236c26cb8f", - "sha256:98b299690d7a0c544bf0f211eabe426ad57751c657139aa4d3333d357c43f638", - "sha256:9c2e5e75c7c3d65131a366e0ae460542a1ace97ae93cc0f283f9efff1a97ebfa", - "sha256:afc696d8e7f3db5cbd32ae13ba53848aa6eec0a85d19ada7363443a27505c651", - "sha256:b6cf0921851b21ee5337e48a487e21a52d629453b42c85a6225776d9300c39ef", - "sha256:b8f4891d4f4772bab7d39f748ffcce39be83dac3bdf93d4e40bbd9e2ce1eb5c4", - "sha256:b96741e5ab3290aae9712e708362d4843e77d8a2418a3af3c08dda14d9691bab", - "sha256:c248be3c030cf7e0d035479b3d8d7ce4e4107959551196413897876c90c77d85", - "sha256:c4a469421402b50e48cc2ffa05229cbdd7dc0164ce2d31eae6b2ac8195ef9634", - "sha256:c5c8578e7fe6aca1dd2a1335ca977efce8bf2d38bc54568f120b912d7ab8d9c7", - "sha256:c7ed3d86d61284d5c0910c8c0988a645cf6723fa2deabe1b149112b2ebe0547e", - "sha256:ccec0368227d02c2ae5dc0881d8075bfa8fa87232f027d375a795d8a0f70c73a", - "sha256:d475cef45640f69ee779720c2fecfcef7cf2a869a0e7e7297ec7854db6cb6a04", - "sha256:d6491e6e623abb27d20f42e37011d28380792ffbba3657bc9fef464c8b5ca1cb", - "sha256:d6f8782c19a0b12b4c5d9cefe1b2ca1636591531c7f20c4ae4d7c5b4077d3af5", - "sha256:d89fcd91a1e0da07b35fbe09f0e10fff880e9ca5f76478f6bbfab10c9c9ef6f2", - "sha256:e0874c097458ce6933cdb1918f2e601e48772d10b8aff478f02d949c757aed97", - "sha256:e185aa34d2ab08e0fe1f6bd60aa6950bfcb84059e7debb8bcfb4f1818eafd6e3", - "sha256:e29accdb21f2186b80b60b07af29d752666fe7bbf057e08c54741740877909b2", - "sha256:e34d93929b4cf93e736be64c0c926af6bf90f0887de0253de233509665d36c9c", - "sha256:e42f7138056b2428cd6197351940ef36d214b8923dbb496af73ac717552cdfec", - "sha256:e4bb67bdef349768f3e662b63d3fc6a939e9e425326acebe4c0c1b48b6f38267", - "sha256:f3765dc391a99564c9b0e6030398a68d5f862e48b96e37fa66ba8101790642e8", - "sha256:f909436b96a032aed0a8529464910ae7665b5826b35801a03d9012dde3fcc9cb", - "sha256:fb2efcf7e942cf58c221adf0b39e8d55d252d2962ed7e8c511f3eec57a903f56" - ], - "version": "==1.15.0rc1" + "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:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6", - "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f" + "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'", - "version": "==2.0.6" + "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:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e", - "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b", - "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7", - "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085", - "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc", - "sha256:3c4129fc3fdc0fa8e40861b5ac0c673315b3c902bbdc05fc176764815b43dd1d", - "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a", - "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498", - "sha256:695104a9223a7239d155d7627ad912953b540929ef97ae0c34c7b8bf30857e89", - "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9", - "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c", - "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7", - "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb", - "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14", - "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af", - "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e", - "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5", - "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06", - "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7" + "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.8" + "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:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647", - "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5" + "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", + "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" ], - "markers": "python_version >= '3.7'", - "version": "==3.1.24" + "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:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15", - "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1" + "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2" ], - "markers": "python_version >= '3.6'", - "version": "==4.8.1" + "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 >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266", + "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2" ], - "markers": "python_version < '4' and python_full_version >= '3.6.1'", - "version": "==5.9.3" + "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:6334aee6073db2fb1f30892697b1730105b5e9a77ce7e61fca6b435225493efe", - "sha256:bd2145a237ed70c8ce72978b497619ddfcae640b6dcf494402d5143e37755c6e" + "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50", + "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b" ], - "markers": "python_version >= '3.6'", - "version": "==23.2.1" - }, - "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:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1" ], - "version": "==0.6.1" + "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:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", - "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", + "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" ], - "markers": "python_version >= '3.6'", - "version": "==1.0.0" + "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:14e99e14e11a14cadf4d99effd4c5c30a9f46b116551ee69b4e5f8f6004f62d5", - "sha256:61b247121581f50c7988eece6ebb2d87ccc3be46c5552daf910d56e20cf6da75" - ], - "markers": "python_version >= '3.5'", - "version": "==3.0.0rc1" + "markers": "python_full_version >= '3.9.0'", + "version": "==3.3.4" }, "pytest": { "hashes": [ - "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", - "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd", + "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1" ], "index": "pypi", - "version": "==6.2.5" + "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:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656", - "sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a", - "sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae", - "sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0", - "sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1", - "sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7", - "sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3", - "sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c", - "sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7", - "sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3", - "sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673", - "sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c", - "sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e", - "sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d", - "sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2", - "sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72", - "sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3", - "sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11", - "sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90", - "sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5", - "sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec", - "sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a", - "sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561", - "sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab", - "sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078", - "sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc", - "sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1", - "sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c", - "sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6", - "sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48", - "sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456", - "sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8", - "sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18", - "sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4", - "sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736", - "sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949", - "sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346", - "sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db", - "sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad", - "sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9", - "sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819" - ], - "version": "==2021.9.24" + "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": [ @@ -900,104 +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:59b58edb7f57b11897f150475e7bc0c39c5381f0b8e3fa9f5c20ce6c89ec4aa1", - "sha256:920ce6259f0b2498aaa4545989536a27e4e4607b8318802d7ddc3a533d3d069e" + "sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d", + "sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857" ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "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:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c", - "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d" + "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.3" + "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" }, "typing-extensions": { "hashes": [ - "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", - "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", - "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + "sha256:be199d06d8f09ca2c9425e3aa04a9afba33e892fe079dea959e72df7f8442343", + "sha256:f933a7b288a919ca97adbff656e52ff81f7ff25d98a2aabb9355ca4090f772fe" ], - "version": "==3.10.0.2" + "markers": "python_version < '3.11'", + "version": "==4.12.0rc1" }, "urllib3": { "hashes": [ - "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", - "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f", + "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.7" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "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 6a1f0b1ed5..8091733992 100644 --- a/clients/python/README.md +++ b/clients/python/README.md @@ -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/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 index 8ca879b113..b30ff0ef2b 100644 --- a/database-migration/.env.example +++ b/database-migration/.env.example @@ -3,4 +3,3 @@ DB_USER= DB_PASS= DB_NAME= ROOT_PASS= -DB_DESCRIPTION= diff --git a/database-migration/Dockerfile b/database-migration/Dockerfile index 364a956ae5..a7dd9c919b 100644 --- a/database-migration/Dockerfile +++ b/database-migration/Dockerfile @@ -1,15 +1,17 @@ -FROM node:alpine +FROM node:25.3.0-alpine ENV NODE_ENV production -WORKDIR /app +WORKDIR /home/node/app -COPY package*.json ./ +COPY package.json . +COPY package-lock.json . +COPY .env.example . +COPY index.js . +COPY database.json . RUN npm ci -COPY . . - USER node CMD ["npm", "start"] 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 index 03cc40611e..05e7ef27c3 100644 --- a/database-migration/database.json +++ b/database-migration/database.json @@ -1,10 +1,10 @@ { "type": "database", - "name": "{{DB_NAME}}", - "url": "{{DB_URL}}", - "rootPassword": "{{ROOT_PASS}}", + "name": "{{dbname}}", + "url": "{{url}}", + "rootPassword": "{{rootPassword}}", "options": [ - { "type": "user", "username": "{{DB_USER}}", "password": "{{DB_PASS}}" }, + { "type": "user", "username": "{{username}}", "password": "{{password}}" }, { "type": "documentcollection", "name": "users", @@ -34,7 +34,7 @@ }, { "type": "documentcollection", - "name": "dkim", + "name": "selectors", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -43,7 +43,7 @@ }, { "type": "documentcollection", - "name": "dkimResults", + "name": "auditLogs", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -52,7 +52,7 @@ }, { "type": "documentcollection", - "name": "dmarc", + "name": "dns", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -61,7 +61,7 @@ }, { "type": "documentcollection", - "name": "spf", + "name": "web", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -70,7 +70,7 @@ }, { "type": "documentcollection", - "name": "https", + "name": "webScan", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -79,7 +79,7 @@ }, { "type": "documentcollection", - "name": "ssl", + "name": "guidanceTags", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -186,17 +186,35 @@ } }, { - "type": "edgecollection", - "name": "affiliations", + "type": "documentcollection", + "name": "organizationSummaries", "options": { - "replicationFactor": 3, - "writeConcern": 1, - "numberOfShards": 6 + "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": "claims", + "name": "domainsToSelectors", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -205,7 +223,7 @@ }, { "type": "edgecollection", - "name": "domainsDKIM", + "name": "affiliations", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -214,7 +232,7 @@ }, { "type": "edgecollection", - "name": "dkimToDkimResults", + "name": "claims", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -223,7 +241,7 @@ }, { "type": "edgecollection", - "name": "domainsDMARC", + "name": "favourites", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -232,7 +250,7 @@ }, { "type": "edgecollection", - "name": "domainsSPF", + "name": "domainsDNS", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -241,7 +259,7 @@ }, { "type": "edgecollection", - "name": "domainsHTTPS", + "name": "domainsWeb", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -250,7 +268,7 @@ }, { "type": "edgecollection", - "name": "domainsSSL", + "name": "webToWebScans", "options": { "replicationFactor": 3, "writeConcern": 1, @@ -334,6 +352,28 @@ } } } + }, + { + "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 index 0a6285e61c..cb3bd22864 100644 --- a/database-migration/index.js +++ b/database-migration/index.js @@ -1,21 +1,42 @@ const { config } = require('dotenv-safe') const { ensure } = require('arango-tools') +const { Database } = require('arangojs') config() -const { DB_DESCRIPTION, DB_NAME, ROOT_PASS, DB_USER, DB_PASS, DB_URL } = - process.env +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: { - ROOT_PASS, - DB_NAME, - DB_USER, - DB_PASS, - DB_URL, + 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 index 526030fcce..5a3668bf91 100644 --- a/database-migration/package-lock.json +++ b/database-migration/package-lock.json @@ -10,204 +10,75 @@ "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": "^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": "^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": "^5.1.0", - "eslint-plugin-react": "^7.24.0", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-standard": "^4.1.0", - "prettier": "^2.3.2" - } - }, - "../../arango-tools": { - "version": "0.6.0", - "license": "MIT", - "dependencies": { - "arangojs": "^7.2.0", - "assign-deep": "^1.0.1", - "json-placeholder-replacer": "^1.0.35" - }, - "devDependencies": { - "dotenv-safe": "^8.2.0", - "eslint": "^7.18.0", - "eslint-config-prettier": "^7.2.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.1.3", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-standard": "^4.1.0", - "jest": "^26.6.3", - "prettier": "^2.2.1" - } - }, - "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/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/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-standard": "^5.0.0", + "prettier": "^2.6.0" } }, "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "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.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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==", + "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": { - "type-fest": "^0.20.2" + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.10.0" } }, - "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==", + "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": ">=10" + "node": ">=12.22" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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" + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "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==", + "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": { @@ -245,155 +116,25 @@ "node": ">= 8" } }, - "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=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.3.tgz", - "integrity": "sha512-ffIvbytTVWz+3keg+Sy94FG1QeOvmV9dP2YSdLFHw/ieLXWCa3U1TYu8IRCOpMv2/SPS8XqhM1+ou1YHsdzKrg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.29.3", - "@typescript-eslint/types": "4.29.3", - "@typescript-eslint/typescript-estree": "4.29.3", - "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.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.3.tgz", - "integrity": "sha512-x+w8BLXO7iWPkG5mEy9bA1iFRnk36p/goVlYobVWHyDw69YmaH9q6eA+Fgl7kYHmFvWlebUTUfhtIg4zbbl8PA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.29.3", - "@typescript-eslint/visitor-keys": "4.29.3" - }, - "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.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.3.tgz", - "integrity": "sha512-s1eV1lKNgoIYLAl1JUba8NhULmf+jOmmeFO1G5MN/RBCyyzg4TIOfIOICVNC06lor+Xmy4FypIIhFiJXOknhIg==", - "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.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.3.tgz", - "integrity": "sha512-45oQJA0bxna4O5TMwz55/TpgjX1YrAPOI/rb6kPgmdnemRZx/dB0rsx+Ku8jpDvqTxcE1C/qEbVHbS3h0hflag==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.29.3", - "@typescript-eslint/visitor-keys": "4.29.3", - "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.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.3.tgz", - "integrity": "sha512-MGGfJvXT4asUTeVs0Q2m+sY63UsfnA+C/FDgBKV3itLBmM9H0u+URcneePtkd0at1YELmZK6HSolCqM4Fzs6yA==", - "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": { - "@typescript-eslint/types": "4.29.3", - "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" + "undici-types": "~6.21.0" } }, "node_modules/acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -426,15 +167,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "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-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -460,55 +192,90 @@ } }, "node_modules/arango-tools": { - "resolved": "../../arango-tools", - "link": true + "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/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, + "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": { - "sprintf-js": "~1.0.2" + "@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/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, + "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": { - "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" + "file-type": "^12.1.0", + "mime-types": "^2.1.24" }, "engines": { - "node": ">= 0.4" + "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" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8.3.0" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, + "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": ">=8" + "node": ">=18" } }, - "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==", + "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.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" + "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" @@ -517,16 +284,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", - "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "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.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1", - "function-bind": "^1.1.1" + "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" @@ -535,13 +302,23 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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": ">=8" + "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": { @@ -560,16 +337,14 @@ "concat-map": "0.0.1" } }, - "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/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", "dev": true, + "peer": true, "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" + "semver": "^7.0.0" } }, "node_modules/call-bind": { @@ -631,7 +406,7 @@ "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=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "node_modules/cross-spawn": { @@ -649,9 +424,9 @@ } }, "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==", + "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" @@ -666,33 +441,25 @@ } }, "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "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.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" - } - }, - "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/doctrine": { @@ -707,6 +474,11 @@ "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", @@ -723,56 +495,37 @@ "dotenv": "^8.2.0" } }, - "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/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/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.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", - "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", + "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", - "get-intrinsic": "^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-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.11.0", + "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.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.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" @@ -781,6 +534,15 @@ "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", @@ -798,67 +560,83 @@ "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": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", "dev": true, "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", + "@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.0.1", + "debug": "^4.3.2", "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", + "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", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "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", - "js-yaml": "^3.13.1", + "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.0.4", + "minimatch": "^3.1.2", "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", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.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==", + "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" @@ -868,9 +646,9 @@ } }, "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==", + "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": [ { @@ -887,10 +665,10 @@ } ], "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" + "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": { @@ -913,16 +691,20 @@ } }, "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==", + "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", - "pkg-dir": "^2.0.0" + "debug": "^3.2.7" }, "engines": { "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/eslint-module-utils/node_modules/debug": { @@ -934,131 +716,77 @@ "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=", + "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": { - "locate-path": "^2.0.0" + "eslint-utils": "^2.0.0", + "regexpp": "^3.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" + "node": ">=8.10.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" + "funding": { + "url": "https://github.com/sponsors/mysticatea" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "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=", + "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": { - "p-limit": "^1.1.0" + "eslint-visitor-keys": "^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-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" + "node": ">=6" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "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==", + "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, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, + "peer": true, "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" + "node": ">=4" } }, "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==", + "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.3", - "array.prototype.flat": "^1.2.4", + "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.6.2", - "find-up": "^2.0.0", + "eslint-module-utils": "^2.7.3", "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" + "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" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, "node_modules/eslint-plugin-import/node_modules/debug": { @@ -1082,98 +810,36 @@ "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=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "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=", + "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": { - "p-limit": "^1.1.0" + "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": ">=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-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" + "node": ">=12.22.0" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - } + "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-node": { @@ -1196,94 +862,75 @@ "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==", + "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": "^10.12.0 || >=12.0.0" + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": "^7.0.0" + "eslint": ">=4.19.1" } }, - "node_modules/eslint-plugin-react": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", - "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==", + "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": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "string.prototype.matchall": "^4.0.5" + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">=4" + "node": ">=6" }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7" + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "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==", + "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": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "node": ">=4" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "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, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" + "bin": { + "semver": "bin/semver.js" } }, - "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==", + "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, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/eslint-plugin-standard": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz", - "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==", + "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": [ { @@ -1304,52 +951,37 @@ } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "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": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/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": "^12.22.0 || ^14.17.0 || >=16.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==", + "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": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=6" + "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": "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": { + "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==", @@ -1358,166 +990,30 @@ "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/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/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/eslint/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/eslint/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/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/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/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==", + "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": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "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==", + "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": "^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/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" + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=0.4.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" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { @@ -1545,9 +1041,9 @@ } }, "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" @@ -1568,22 +1064,6 @@ "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-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1593,13 +1073,13 @@ "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=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "node_modules/fastq": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", - "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", + "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" @@ -1617,16 +1097,20 @@ "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==", + "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": { - "to-regex-range": "^5.0.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { @@ -1643,15 +1127,15 @@ } }, "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "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": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "node_modules/function-bind": { @@ -1660,36 +1144,73 @@ "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/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.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "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.1" + "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.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "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" }, @@ -1701,50 +1222,57 @@ } }, "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==", + "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.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "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/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "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": { - "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" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=10" + "node": ">=8" }, "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==", + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, - "engines": { - "node": ">= 4" + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { @@ -1760,9 +1288,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" @@ -1777,10 +1305,22 @@ "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.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "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" @@ -1804,16 +1344,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -1835,19 +1369,10 @@ "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/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { "node": ">=0.8.19" @@ -1856,7 +1381,7 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { "once": "^1.3.0", @@ -1870,12 +1395,12 @@ "dev": true }, "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==", + "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.0", + "get-intrinsic": "^1.1.3", "has": "^1.0.3", "side-channel": "^1.0.4" }, @@ -1883,12 +1408,6 @@ "node": ">= 0.4" } }, - "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", @@ -1918,9 +1437,9 @@ } }, "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==", + "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" @@ -1930,9 +1449,9 @@ } }, "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==", + "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" @@ -1959,25 +1478,21 @@ "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=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "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-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" @@ -1987,9 +1502,9 @@ } }, "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" @@ -1998,19 +1513,10 @@ "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==", + "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" @@ -2022,6 +1528,15 @@ "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", @@ -2038,6 +1553,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-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -2068,37 +1595,46 @@ "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": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "node_modules/js-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": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "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": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, - "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-placeholder-replacer": { "version": "1.0.35", "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", @@ -2117,81 +1653,61 @@ "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=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/jsx-ast-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", - "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "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": { - "array-includes": "^3.1.2", - "object.assign": "^4.1.2" + "minimist": "^1.2.0" }, - "engines": { - "node": ">=4.0" + "bin": { + "json5": "lib/cli.js" } }, - "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=", + "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": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=4" + "node": ">= 0.8.0" } }, - "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=", + "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": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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.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.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/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==", - "dev": true, - "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==", "dev": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -2199,32 +1715,37 @@ "node": ">=10" } }, - "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, + "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": ">= 8" + "node": ">= 0.6" } }, - "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, + "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": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "mime-db": "1.52.0" }, "engines": { - "node": ">=8.6" + "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.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" @@ -2234,10 +1755,13 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "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", @@ -2245,46 +1769,24 @@ "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": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "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/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "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==", + "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" @@ -2300,14 +1802,14 @@ } }, "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==", + "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.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { @@ -2317,62 +1819,77 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.entries": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", - "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", + "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.3", - "es-abstract": "^1.18.2" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.fromentries": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz", - "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "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.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8.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==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "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": { - "wrappy": "1" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parent-module": { @@ -2387,23 +1904,24 @@ "node": ">=6" } }, - "node_modules/parse-json": { + "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/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, "engines": { - "node": ">=4" + "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=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2424,153 +1942,38 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "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": "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/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/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/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/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/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/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/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=", + "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": ">=4" + "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "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/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "engines": { - "node": ">=0.4.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "node": ">= 0.6.0" } }, - "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==", - "dev": true - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -2600,162 +2003,61 @@ } ] }, - "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=", + "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": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.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-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=", + "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, - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "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/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": { - "locate-path": "^2.0.0" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=4" + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/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, - "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/regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "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/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/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -2804,13 +2106,34 @@ "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": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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": { @@ -2848,141 +2171,55 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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": "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/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/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/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.matchall": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", - "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "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==", + "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.3" + "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.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "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.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" }, "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.0" + "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", @@ -3007,146 +2244,80 @@ "node": ">=8" } }, - "node_modules/table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "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, - "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.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "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" + "node": ">= 0.4" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/ljharb" } }, - "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/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/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==", + "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==", + "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": { - "minimist": "^1.2.0" + "prelude-ls": "^1.2.1" }, - "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": ">= 0.8.0" } }, - "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/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "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, - "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/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "node": ">=10" }, - "engines": { - "node": ">=4.2.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "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": { "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", @@ -3156,22 +2327,6 @@ "punycode": "^2.1.0" } }, - "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/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/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3204,9 +2359,9 @@ } }, "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==", + "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" @@ -3215,141 +2370,95 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "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 + "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": { - "@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/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" - }, - "dependencies": { - "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" - } - }, - "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" - } - }, - "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 - }, - "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 - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "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" - } - } - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "@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.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "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 - } } }, "@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==", + "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.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "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.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "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": { @@ -3378,101 +2487,25 @@ "fastq": "^1.6.0" } }, - "@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 - }, - "@typescript-eslint/experimental-utils": { - "version": "4.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.3.tgz", - "integrity": "sha512-ffIvbytTVWz+3keg+Sy94FG1QeOvmV9dP2YSdLFHw/ieLXWCa3U1TYu8IRCOpMv2/SPS8XqhM1+ou1YHsdzKrg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.29.3", - "@typescript-eslint/types": "4.29.3", - "@typescript-eslint/typescript-estree": "4.29.3", - "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.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.3.tgz", - "integrity": "sha512-x+w8BLXO7iWPkG5mEy9bA1iFRnk36p/goVlYobVWHyDw69YmaH9q6eA+Fgl7kYHmFvWlebUTUfhtIg4zbbl8PA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.29.3", - "@typescript-eslint/visitor-keys": "4.29.3" - } - }, - "@typescript-eslint/types": { - "version": "4.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.3.tgz", - "integrity": "sha512-s1eV1lKNgoIYLAl1JUba8NhULmf+jOmmeFO1G5MN/RBCyyzg4TIOfIOICVNC06lor+Xmy4FypIIhFiJXOknhIg==", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@typescript-eslint/typescript-estree": { - "version": "4.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.3.tgz", - "integrity": "sha512-45oQJA0bxna4O5TMwz55/TpgjX1YrAPOI/rb6kPgmdnemRZx/dB0rsx+Ku8jpDvqTxcE1C/qEbVHbS3h0hflag==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.29.3", - "@typescript-eslint/visitor-keys": "4.29.3", - "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.29.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.3.tgz", - "integrity": "sha512-MGGfJvXT4asUTeVs0Q2m+sY63UsfnA+C/FDgBKV3itLBmM9H0u+URcneePtkd0at1YELmZK6HSolCqM4Fzs6yA==", - "dev": true, + "@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", "requires": { - "@typescript-eslint/types": "4.29.3", - "eslint-visitor-keys": "^2.0.0" + "undici-types": "~6.21.0" } }, "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", - "dev": true, - "peer": true + "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", @@ -3493,12 +2526,6 @@ "uri-js": "^4.2.2" } }, - "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-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3515,80 +2542,103 @@ } }, "arango-tools": { - "version": "file:../../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", - "dotenv-safe": "^8.2.0", - "eslint": "^7.18.0", - "eslint-config-prettier": "^7.2.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.1.3", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-standard": "^4.1.0", - "jest": "^26.6.3", - "json-placeholder-replacer": "^1.0.35", - "prettier": "^2.2.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" + } + } } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, + "arangojs": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-9.2.0.tgz", + "integrity": "sha512-WIoFPCrBeuX1kBzGwue4t+jjYFW/ipto4ZO5s6e1v6dMsZE4I+d5Bid2glL+D5UeUJ6qGgqfe3KOAAW1t0Pc2A==", "requires": { - "sprintf-js": "~1.0.2" + "@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.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "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.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" } }, - "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.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==", + "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.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" } }, - "array.prototype.flatmap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", - "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", - "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": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1", - "function-bind": "^1.1.1" + "assign-symbols": "^2.0.2" } }, - "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 + "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", @@ -3606,13 +2656,14 @@ "concat-map": "0.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", "dev": true, + "peer": true, "requires": { - "fill-range": "^7.0.1" + "semver": "^7.0.0" } }, "call-bind": { @@ -3659,7 +2710,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "cross-spawn": { @@ -3674,36 +2725,28 @@ } }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "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.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "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.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" - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dev": true, "requires": { - "path-type": "^4.0.0" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "doctrine": { @@ -3715,6 +2758,11 @@ "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", @@ -3728,53 +2776,46 @@ "dotenv": "^8.2.0" } }, - "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 - }, - "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" - } - }, - "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.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", - "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", + "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", - "get-intrinsic": "^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-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.11.0", + "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.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.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": { @@ -3788,145 +2829,75 @@ "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": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", "dev": true, "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", + "@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.0.1", + "debug": "^4.3.2", "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", + "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", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "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", - "js-yaml": "^3.13.1", + "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.0.4", + "minimatch": "^3.1.2", "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", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", "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" - } - }, - "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" - } - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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" - } - }, - "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 - } + "text-table": "^0.2.0" } }, "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==", + "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": "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==", + "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": {} }, @@ -3952,13 +2923,12 @@ } }, "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==", + "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", - "pkg-dir": "^2.0.0" + "debug": "^3.2.7" }, "dependencies": { "debug": { @@ -3969,98 +2939,58 @@ "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 - }, - "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==", + "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.24.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", - "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", + "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.3", - "array.prototype.flat": "^1.2.4", + "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.6.2", - "find-up": "^2.0.0", + "eslint-module-utils": "^2.7.3", "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" + "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": { @@ -4081,70 +3011,29 @@ "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": { + "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=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true } } }, - "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==", + "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": { - "@typescript-eslint/experimental-utils": "^4.0.1" + "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": { @@ -4161,148 +3050,97 @@ "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-plugin-react": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", - "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==", - "dev": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "string.prototype.matchall": "^4.0.5" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "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": { - "esutils": "^2.0.2" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" } }, - "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==", + "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": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "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-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==", + "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": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz", - "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==", + "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": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "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": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - } + "estraverse": "^5.2.0" } }, "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "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": "^1.1.0" + "eslint-visitor-keys": "^2.0.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==", + "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": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "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": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "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": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "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 - } + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, - "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", @@ -4322,9 +3160,9 @@ } }, "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 }, "esutils": { @@ -4339,19 +3177,6 @@ "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" - } - }, "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", @@ -4361,13 +3186,13 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastq": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", - "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", + "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" @@ -4382,13 +3207,14 @@ "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==", + "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": { - "to-regex-range": "^5.0.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, "flat-cache": { @@ -4402,15 +3228,15 @@ } }, "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "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": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "function-bind": { @@ -4419,72 +3245,99 @@ "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=", + "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.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "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.1" + "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.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "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.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "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.1" + "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" } }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "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 - } + "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" } }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "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": { @@ -4497,9 +3350,9 @@ } }, "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": { @@ -4508,10 +3361,19 @@ "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.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "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": { @@ -4523,16 +3385,10 @@ "has-symbols": "^1.0.2" } }, - "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 - }, "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "import-fresh": { @@ -4543,26 +3399,18 @@ "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 - } } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -4576,22 +3424,16 @@ "dev": true }, "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "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.0", + "get-intrinsic": "^1.1.3", "has": "^1.0.3", "side-channel": "^1.0.4" } }, - "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", @@ -4612,15 +3454,15 @@ } }, "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==", + "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.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "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" @@ -4638,45 +3480,44 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "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-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-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==", + "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.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "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", @@ -4687,6 +3528,15 @@ "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", @@ -4705,34 +3555,36 @@ "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": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "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": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "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": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, - "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-placeholder-replacer": { "version": "1.0.35", "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", @@ -4747,44 +3599,36 @@ "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=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "jsx-ast-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", - "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "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": { - "array-includes": "^3.1.2", - "object.assign": "^4.1.2" + "minimist": "^1.2.0" } }, - "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=", + "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": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, - "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 + "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", @@ -4792,59 +3636,50 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "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 - }, - "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==", - "dev": true, - "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==", "dev": true, + "peer": true, "requires": { "yallist": "^4.0.0" } }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, + "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": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "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==", + "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.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, "ms": { @@ -4853,42 +3688,21 @@ "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": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "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 - } - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "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": { @@ -4898,58 +3712,67 @@ "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==", + "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.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, - "object.entries": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", - "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", + "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.3", - "es-abstract": "^1.18.2" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, - "object.fromentries": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz", - "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "wrappy": "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==", + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" + "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" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "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": { - "wrappy": "1" + "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": { @@ -4961,20 +3784,21 @@ "callsites": "^3.0.0" } }, - "parse-json": { + "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/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" - } + "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=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "path-key": { @@ -4989,114 +3813,22 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "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": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "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 }, - "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" - }, - "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 - } - } - }, "prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", "dev": true }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "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==", - "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", @@ -5110,97 +3842,15 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "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" - } - } - } - }, - "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 - } - } - }, "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "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" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpp": { @@ -5209,22 +3859,23 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "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==", + "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.2.0", - "path-parse": "^1.0.6" + "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", @@ -5249,11 +3900,26 @@ "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": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "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", @@ -5281,117 +3947,43 @@ "object-inspect": "^1.9.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "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" - } - }, - "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 - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "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.matchall": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", - "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", - "side-channel": "^1.0.4" - } - }, "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==", + "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.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "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==", + "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.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "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, "requires": { - "ansi-regex": "^5.0.0" + "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", @@ -5407,118 +3999,62 @@ "has-flag": "^4.0.0" } }, - "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.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "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 - } - } + "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": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "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" - } - }, "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==", + "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.0", + "minimist": "^1.2.6", "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": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "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": { - "tslib": "^1.8.1" + "prelude-ls": "^1.2.1" } }, - "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, - "peer": 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 }, "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, "requires": { - "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" } }, + "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", @@ -5528,22 +4064,6 @@ "punycode": "^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 - }, - "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" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5567,21 +4087,49 @@ } }, "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.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": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "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 index 9623d760b9..47fbdedad3 100644 --- a/database-migration/package.json +++ b/database-migration/package.json @@ -4,7 +4,7 @@ "description": "creates the database according to a setup file", "main": "index.js", "scripts": { - "lint": "eslint src", + "lint": "eslint", "start": "node --dns-result-order=ipv4first --experimental-json-modules --experimental-vm-modules index.js" }, "keywords": [], @@ -12,20 +12,18 @@ "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": "^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": "^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": "^5.1.0", - "eslint-plugin-react": "^7.24.0", - "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-standard": "^4.1.0", - "prettier": "^2.3.2" + "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 1d81704117..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 -t ed25519 -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/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 c23d0dd3cf..0000000000 --- a/deploy/bases/flux.yaml +++ /dev/null @@ -1,5394 +0,0 @@ ---- -# Flux version: v0.19.1 -# 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.19.1 - name: flux-system ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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: - default: - observedGeneration: -1 - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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 - - gcp - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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 - include: - description: Extra git repositories to map into the repository - items: - description: GitRepositoryInclude defines a source with a from and - to path. - properties: - fromPath: - description: The path to copy contents from, defaults to the - root directory. - type: string - repository: - description: Reference to a GitRepository to include. - properties: - name: - description: Name of the referent - type: string - required: - - name - type: object - toPath: - description: The path to copy contents to, defaults to the name - of the source ref. - type: string - required: - - repository - type: object - type: array - 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 - includedArtifacts: - description: IncludedArtifacts represents the included artifacts from - the last successful repository sync. - items: - description: Artifact represents the output of a source synchronisation. - 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 - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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 - reconcileStrategy: - default: ChartVersion - description: Determines what enables the creation of a new artifact. - Valid values are ('ChartVersion', 'Revision'). See the documentation - of the values for an explanation on their behavior. Defaults to - ChartVersion when omitted. - enum: - - ChartVersion - - Revision - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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 - passCredentials: - description: PassCredentials allows the credentials from the SecretRef - to be passed on to a host that does not match the host as defined - in URL. This may be required if the host of the advertised chart - URLs in the index differ from the defined URL. Enabling this should - be done with caution, as it can potentially result in credentials - getting stolen in a MITM-attack. - type: boolean - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.latestImage - name: LatestImage - type: string - name: v1beta1 - 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 - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.lastScanResult.scanTime - name: Last scan - type: string - - jsonPath: .status.lastScanResult.tagCount - name: Tags - type: string - name: v1beta1 - 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: - accessFrom: - description: AccessFrom defines an ACL for allowing cross-namespace - references to the ImageRepository object based on the caller's namespace - labels. - properties: - namespaceSelectors: - items: - properties: - matchLabels: - additionalProperties: - type: string - type: object - type: object - type: array - type: object - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.lastAutomationRunTime - name: Last run - type: string - name: v1beta1 - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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 - patches: - description: Strategic merge and JSON patches, defined as inline YAML - objects, capable of targeting objects based on kind, label and annotation - selectors. - items: - description: Patch contains either a StrategicMerge or a JSON6902 - patch, either a file or inline, and the target the patch should - be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch document with - an array of operation objects. - type: string - 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 - type: object - type: array - 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: - default: - observedGeneration: -1 - 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: false - subresources: - status: {} - - 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: v1beta2 - 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 configuration to calculate - the desired state from a Source using Kustomize. - 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 - patches: - description: Strategic merge and JSON patches, defined as inline YAML - objects, capable of targeting objects based on kind, label and annotation - selectors. - items: - description: Patch contains either a StrategicMerge or a JSON6902 - patch, either a file or inline, and the target the patch should - be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch document with - an array of operation objects. - type: string - 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 - type: object - type: array - patchesJson6902: - description: 'JSON 6902 patches, defined as inline YAML objects. Deprecated: - Use Patches instead.' - 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. - Deprecated: Use Patches instead.' - 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 namespace - of the Kubernetes resource object that contains the reference. - 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: 'Deprecated: Not used in v1beta2.' - enum: - - none - - client - - server - type: string - wait: - description: Wait instructs the controller to check the health of - all the reconciled resources. When enabled, the HealthChecks are - ignored. Defaults to false. - type: boolean - required: - - interval - - prune - - sourceRef - type: object - status: - default: - observedGeneration: -1 - 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 - inventory: - description: Inventory contains the list of Kubernetes resource object - references that have been successfully applied. - properties: - entries: - description: Entries of Kubernetes resource object references. - items: - description: ResourceRef contains the information necessary - to locate a resource within a cluster. - properties: - id: - description: ID is the string representation of the Kubernetes - resource object's metadata, in the format '___'. - type: string - v: - description: Version is the API version of the Kubernetes - resource object's kind. - type: string - required: - - id - - v - type: object - type: array - required: - - entries - type: object - 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 - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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 - - azureeventhub - - telegram - - lark - - matrix - - opsgenie - - alertmanager - type: string - username: - description: Bot username for this provider - type: string - required: - - type - type: object - status: - default: - observedGeneration: -1 - 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 - 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.5.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.19.1 - 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: - default: - observedGeneration: -1 - 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.19.1 - 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.19.1 - 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.19.1 - 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.19.1 - 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.19.1 - 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.19.1 - 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: - - namespaces - - 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.19.1 - 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.19.1 - 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.19.1 - 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.19.1 - 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.19.1 - 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.19.1 - 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.15.0 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 8080 - name: http-prom - - containerPort: 9440 - name: healthz - protocol: TCP - 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.19.1 - 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.13.0 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 8080 - name: http-prom - - containerPort: 9440 - name: healthz - protocol: TCP - 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.19.1 - 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.16.0 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 8080 - name: http-prom - - containerPort: 9440 - name: healthz - protocol: TCP - 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.19.1 - 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.18.1 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 9090 - name: http - - containerPort: 9292 - name: http-webhook - - containerPort: 8080 - name: http-prom - - containerPort: 9440 - name: healthz - protocol: TCP - 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.19.1 - 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.16.1 - 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.19.1 - 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.19.1 - 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.19.1 - 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/kustomization.yaml b/deploy/bases/kustomization.yaml deleted file mode 100644 index 39eb59db42..0000000000 --- a/deploy/bases/kustomization.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- flux.yaml -- api-registry.yaml -- frontend-registry.yaml -- dmarc-report-registry.yaml -- tracker-repo.yaml -- app-kustomization.yaml -- platform-kustomization.yaml -- summaries-registry.yaml -- guidance-registry.yaml -- super-admin-registry.yaml -- scanner-registries.yaml -- scanners-kustomization.yaml -- log4shell-processor-registry.yaml -- log4shell-scanner-registry.yaml diff --git a/deploy/bases/log4shell-processor-registry.yaml b/deploy/bases/log4shell-processor-registry.yaml deleted file mode 100644 index eed7fe7394..0000000000 --- a/deploy/bases/log4shell-processor-registry.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: log4shell-processor - namespace: flux-system -spec: - image: gcr.io/track-compliance/log4shell-processor - interval: 1m0s - diff --git a/deploy/bases/log4shell-scanner-registry.yaml b/deploy/bases/log4shell-scanner-registry.yaml deleted file mode 100644 index 518d9cb9df..0000000000 --- a/deploy/bases/log4shell-scanner-registry.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: log4shell-scanner - namespace: flux-system -spec: - image: gcr.io/track-compliance/log4shell-scanner - interval: 1m0s - 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/scanner-registries.yaml b/deploy/bases/scanner-registries.yaml deleted file mode 100644 index ce6565f5bd..0000000000 --- a/deploy/bases/scanner-registries.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: tls-scanner - namespace: flux-system -spec: - image: gcr.io/track-compliance/tls-scanner - interval: 5m0s ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: dns-scanner - namespace: flux-system -spec: - image: gcr.io/track-compliance/dns-scanner - interval: 5m0s ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: https-scanner - namespace: flux-system -spec: - image: gcr.io/track-compliance/https-scanner - interval: 5m0s ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: tls-processor - namespace: flux-system -spec: - image: gcr.io/track-compliance/tls-processor - interval: 5m0s ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: dns-processor - namespace: flux-system -spec: - image: gcr.io/track-compliance/dns-processor - interval: 5m0s ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: https-processor - namespace: flux-system -spec: - image: gcr.io/track-compliance/https-processor - interval: 5m0s ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha2 -kind: ImageRepository -metadata: - name: domain-dispatcher - namespace: flux-system -spec: - image: gcr.io/track-compliance/domain-dispatcher - interval: 5m0s ---- diff --git a/deploy/bases/scanners-kustomization.yaml b/deploy/bases/scanners-kustomization.yaml deleted file mode 100644 index c3d2e5fa26..0000000000 --- a/deploy/bases/scanners-kustomization.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 -kind: Kustomization -metadata: - name: scanners - namespace: flux-system -spec: - interval: 2m0s - path: ./scanners - prune: true - sourceRef: - kind: GitRepository - name: tracker 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/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/kustomization.yaml b/deploy/gke/kustomization.yaml deleted file mode 100644 index 91ccdd4a3b..0000000000 --- a/deploy/gke/kustomization.yaml +++ /dev/null @@ -1,20 +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 -- 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 -- scanners-image-policies.yaml -- log4shell-processor-image-policy.yaml -- log4shell-scanner-image-policy.yaml -patchesStrategicMerge: -- tracker-repo.yaml diff --git a/deploy/gke/log4shell-processor-image-policy.yaml b/deploy/gke/log4shell-processor-image-policy.yaml deleted file mode 100644 index 6ad88633a2..0000000000 --- a/deploy/gke/log4shell-processor-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: log4shell-processor - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: log4shell-processor - policy: - numerical: - order: asc - diff --git a/deploy/gke/log4shell-scanner-image-policy.yaml b/deploy/gke/log4shell-scanner-image-policy.yaml deleted file mode 100644 index 90dd853a09..0000000000 --- a/deploy/gke/log4shell-scanner-image-policy.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: log4shell-scanner - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: log4shell-scanner - policy: - numerical: - order: asc - 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/scanners-image-policies.yaml b/deploy/gke/scanners-image-policies.yaml deleted file mode 100644 index b0a75140e5..0000000000 --- a/deploy/gke/scanners-image-policies.yaml +++ /dev/null @@ -1,105 +0,0 @@ ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: tls-scanner - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: tls-scanner - policy: - numerical: - order: asc ---- -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 ---- -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 ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: tls-processor - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: tls-processor - policy: - numerical: - order: asc ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: dns-processor - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: dns-processor - policy: - numerical: - order: asc ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: https-processor - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: https-processor - policy: - numerical: - order: asc ---- -apiVersion: image.toolkit.fluxcd.io/v1alpha1 -kind: ImagePolicy -metadata: - name: domain-dispatcher - namespace: flux-system -spec: - filterTags: - extract: $ts - pattern: ^master-[a-fA-F0-9]+-(?P.*) - imageRepositoryRef: - name: domain-dispatcher - 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/log4shell-scanner-image-policy.yaml b/deploy/log4shell-scanner-image-policy.yaml deleted file mode 100644 index 4163e72242..0000000000 --- a/deploy/log4shell-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/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 7aad2a6e93..96809bf101 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:17-alpine as build-env +FROM node:20.18.1-alpine3.19 as build-env WORKDIR /app @@ -7,9 +7,7 @@ COPY . . RUN npm ci && npm run build && npm prune --production -# https://github.com/astefanutti/scratch-node -FROM astefanutti/scratch-node:17 -LABEL maintainer="mike.williamson@tbs-sct.gc.ca" +FROM node:20.18.1-alpine3.19 ENV HOST 0.0.0.0 ENV PORT 3000 @@ -22,8 +20,7 @@ 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 7fb1ab45ec..0c68fb760f 100644 --- a/frontend/cloudbuild.yaml +++ b/frontend/cloudbuild.yaml @@ -1,63 +1,78 @@ steps: - - name: node:16.3.0-alpine + - name: node:20.10.0-alpine id: install dir: frontend entrypoint: npm args: ['ci', '--no-optional'] - - name: node:16.3.0-alpine + - name: node:20.10.0-alpine id: lint dir: frontend entrypoint: npm args: ['run', 'lint'] - - name: node:16.3.0-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:16.3.0-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:16.3.0-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:16.3.0-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 - image=$(cat /workspace/imagename) - docker build -t $image . - else - exit 0 - fi + image=$(cat /workspace/imagename) + docker build -t $image . - name: 'gcr.io/cloud-builders/docker' id: push-if-master diff --git a/frontend/docker-compose.yaml b/frontend/docker-compose.yaml index 06e8ce26fe..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:16.3.0-alpine - working_dir: /app - command: npm run dev - volumes: - - ./:/app - expose: - - "3000" - mocked_api: # available at localhost:3000/graphql - image: node:16.3.0-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 1d39c51767..cd1b399b2f 100644 --- a/frontend/mocking/faked_schema.js +++ b/frontend/mocking/faked_schema.js @@ -2,3145 +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. - webSummary: CategorizedSummary + """ + SPF summary computed values, used to build summary cards. + """ + spfSummary: CategorizedSummary - # DMARC summary computed values, used to build summary cards. - dmarcSummary: CategorizedSummary + """ + SSL summary computed values, used to build summary cards. + """ + sslSummary: CategorizedSummary - # DMARC phase summary computed values, used to build summary cards. - dmarcPhaseSummary: CategorizedSummary + """ + Web connections (HTTPS + HSTS) summary computed values, used to build summary cards. + """ + webConnectionsSummary: CategorizedSummary - # HTTPS summary computed values, used to build summary cards. - httpsSummary: CategorizedSummary + """ + Web summary computed values, used to build summary cards. + """ + webSummary: CategorizedSummary + + """ + Select domains a user has access to. + """ + findChartSummaries( + """ + The month in which the returned data is relevant to. + """ + month: PeriodEnums! - # Query the currently logged in user. + """ + 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 - # Checks if user must be logged in to access data. + """ + Checks if user must be logged in to access data. + """ loginRequired: Boolean - # Query used to check if the user has an admin role. + """ + 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. + """ + 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 { - # Ciphers Status + """ + Certificates Status + """ + certificates: StatusEnum + + """ + Ciphers Status + """ ciphers: StatusEnum - # Curves Status + """ + Curves Status + """ curves: StatusEnum - # DKIM Status + """ + DKIM Status + """ dkim: StatusEnum - # DMARC Status + """ + DMARC Status + """ dmarc: StatusEnum - # HTTPS Status + """ + HTTPS Status + """ https: StatusEnum - # HSTS Status + """ + HSTS Status + """ hsts: StatusEnum - # Policy Status + """ + Policy Status + """ policy: StatusEnum - # Protocols Status + """ + Protocols Status + """ protocols: StatusEnum - # SPF Status + """ + 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 - # Whether 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 consists of upper case or lower case letters or underscores with 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 whose 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 DMARC 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. + """ + Summary based on HTTPS scan results for a given organization. + """ https: CategorizedSummary - # Summary based on mail scan results for a given organization. + """ + 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. + """ + 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! - - # The ordering direction. - direction: OrderDirection! - } - - # Properties by which domain connections can be ordered. - enum DomainOrderField { - # Order domains by ciphers status. - CIPHERS_STATUS - - # Order domains by curves status. - CURVES_STATUS - - # Order domains by domain. - DOMAIN + """ + An enum used to select information from the dmarc-report-api. + """ + enum PeriodEnums { + """ + The month of January. + """ + JANUARY - # Order domains by dkim status. - DKIM_STATUS + """ + The month of February. + """ + FEBRUARY - # Order domains by dmarc status. - DMARC_STATUS + """ + The month of March. + """ + MARCH - # Order domains by https status. - HTTPS_STATUS + """ + The month of April. + """ + APRIL - # Order domains by hsts status. - HSTS_STATUS + """ + The month of May. + """ + MAY - # Order domains by ITPIN policy status. - POLICY_STATUS + """ + The month of June. + """ + JUNE - # Order domains by protocols status. - PROTOCOLS_STATUS + """ + The month of July. + """ + JULY - # Order domains by spf status. - SPF_STATUS - } + """ + The month of August. + """ + AUGUST - # 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 + """ + The month of September. + """ + SEPTEMBER - # Specifies a descending order for a given \`orderBy\` argument. - DESC - } + """ + The month of October. + """ + OCTOBER - # A connection to a list of items. - type AffiliationConnection { - # Information to aid in pagination. - pageInfo: PageInfo! + """ + The month of November. + """ + NOVEMBER - # A list of edges. - edges: [AffiliationEdge] + """ + The month of December. + """ + DECEMBER - # The total amount of affiliations the user has access to. - totalCount: Int - } + """ + The last 30 days. + """ + LAST30DAYS - # An edge in a connection. - type AffiliationEdge { - # The item at the end of the edge - node: Affiliation + """ + The last year. + """ + LASTYEAR - # A cursor for use in pagination - cursor: String! + """ + The year to date. + """ + YTD } - # 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 - id: ID! - - # User's level of access to a given organization. - permission: RoleEnums - - # The affiliated users information. - user: SharedUser + """ + A field that conforms to a 4 digit integer. + """ + scalar Year - # The affiliated organizations information. - organization: Organization - } + """ + 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 certificates status. + """ + CERTIFICATES_STATUS + + """ + Order domains by ciphers status. + """ + CIPHERS_STATUS - # An enum used to assign, and test users roles. - enum RoleEnums { - # A user who has been given access to view an organization. - USER + """ + Order domains by curves status. + """ + CURVES_STATUS - # A user who has the same access as a user write account, but can define new user read/write accounts. - ADMIN + """ + Order domains by domain. + """ + DOMAIN - # A user who has the same access as an admin, but can define new admins. - SUPER_ADMIN - } + """ + Order domains by dkim status. + """ + DKIM_STATUS - # 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 - id: ID! + """ + Order domains by dmarc status. + """ + DMARC_STATUS - # Users display name. - displayName: String + """ + Order domains by https status. + """ + HTTPS_STATUS - # Users email address. - userName: EmailAddress - } + """ + Order domains by hsts status. + """ + HSTS_STATUS - # A field whose value conforms to the standard internet email address format as specified in RFC822: https://www.w3.org/Protocols/rfc822/. - scalar EmailAddress + """ + Order domains by ITPIN policy status. + """ + POLICY_STATUS - # Ordering options for affiliation connections. - input AffiliationUserOrder { - # The field to order affiliations by. - field: AffiliationUserOrderField! + """ + Order domains by protocols status. + """ + PROTOCOLS_STATUS - # The ordering direction. - direction: OrderDirection! - } + """ + Order domains by spf status. + """ + SPF_STATUS - # Properties by which affiliation connections can be ordered. - enum AffiliationUserOrderField { - # Order affiliation edges by username. - USER_USERNAME + """ + Order domains by tags. + """ + TAGS } - # Ordering options for organization connections - input OrganizationOrder { - # The field to order organizations by. - field: OrganizationOrderField! + """ + """ + enum ComparisonEnums { + """ + """ + EQUAL - # The ordering direction. - direction: OrderDirection! + """ + """ + NOT_EQUAL } - # Properties by which organization connections can be ordered. - enum OrganizationOrderField { - # Order organizations by acronym. - ACRONYM - - # Order organizations by name. - NAME - - # Order organizations by slug. - SLUG - - # Order organizations by zone. - ZONE - - # Order organizations by sector. - SECTOR - - # Order organizations by country. - COUNTRY - - # Order organizations by province. - PROVINCE - - # Order organizations by city. - CITY - - # Order organizations by verified. - VERIFIED + """ + """ + enum filterValueEnums { + """ + If the given check meets the passing requirements. + """ + PASS - # 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 + """ + If the given check has flagged something that can provide information on the domain that aren't scan related. + """ + INFO - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection + """ + If the given check does not meet the passing requirements + """ + FAIL - # Positive guidance tags found during scan. - positiveGuidanceTags( - # Ordering options for guidance tag connections - orderBy: GuidanceTagOrder + """ + 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! - # Returns the items in the list that come after the specified cursor. - after: String + """ + A list of edges. + """ + edges: [DomainEdge] - # Returns the first n items from the list. - first: Int + """ + The total amount of domains the user has access to. + """ + totalCount: Int + } - # Returns the items in the list that come before the specified cursor. - before: String + """ + An edge in a connection. + """ + type DomainEdge { + """ + The item at the end of the edge + """ + node: Domain - # Returns the last n items from the list. - last: Int - ): GuidanceTagConnection + """ + A cursor for use in pagination + """ + cursor: String! } - # Ordering options for SPF connections. - input SPFOrder { - # The field to order SPF scans by. - field: SPFOrderField! + """ + Ordering options for domain connections. + """ + input DomainOrder { + """ + The field to order domains by. + """ + field: DomainOrderField! - # The ordering direction. + """ + The ordering direction. + """ direction: OrderDirection! } - # Properties by which SPF connections can be ordered. - enum SPFOrderField { - # Order SPF edges by timestamp. - TIMESTAMP + """ + A connection to a list of items. + """ + type AffiliationConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of edges. + """ + edges: [AffiliationEdge] - # Order SPF edges by lookups. - LOOKUPS + """ + The total amount of affiliations the user has access to. + """ + totalCount: Int + } - # Order SPF edges by record. - RECORD + """ + An edge in a connection. + """ + type AffiliationEdge { + """ + The item at the end of the edge + """ + node: Affiliation - # Order SPF edges by spf-default. - SPF_DEFAULT + """ + A cursor for use in pagination + """ + cursor: String! } - # Results of HTTPS, and SSL scan on the given domain. - type WebScan { - # The domain the scan was ran on. - domain: Domain + """ + 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 + """ + id: ID! - # Hyper Text Transfer Protocol Secure scan results. - https( - # Start date for date filter. - startDate: Date + """ + User's level of access to a given organization. + """ + permission: RoleEnums - # End date for date filter. - endDate: Date + """ + The affiliated users information. + """ + user: SharedUser - # Ordering options for https connections. - orderBy: HTTPSOrder + """ + The affiliated organizations information. + """ + organization: Organization + } - # Returns the items in the list that come after the specified cursor. - after: String + """ + 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 + """ + id: ID! - # Returns the first n items from the list. - first: Int + """ + Users display name. + """ + displayName: String - # Returns the items in the list that come before the specified cursor. - before: String + """ + Users email address. + """ + userName: EmailAddress - # Returns the last n items from the list. - last: Int - ): HTTPSConnection + """ + Has the user email verified their account. + """ + emailValidated: Boolean - # Secure Socket Layer scan results. - ssl( - # Start date for date filter. - startDate: Date + """ + Does the user want to see new features in progress. + """ + insideUser: Boolean - # End date for date filter. - endDate: Date + """ + Users affiliations to various organizations. + """ + affiliations( + """ + Ordering options for affiliation connections. + """ + orderBy: AffiliationOrgOrder - # Ordering options for ssl connections. - orderBy: SSLOrder + """ + 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 - ): SSLConnection + ): AffiliationConnection } - # 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 + """ + 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 February. - FEBRUARY + """ + The domain string the scan was ran on. + """ + domain: String - # The month of March. - MARCH + """ + The time when the scan was initiated. + """ + timestamp: DateTime - # The month of April. - APRIL + """ + Results of the web scan at each IP address. + """ + results: [WebScan] + } - # The month of May. - MAY + """ + 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 June. - JUNE + """ + 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 July. - JULY + """ + Whether or not the scanned server is vulnerable to heartbleed. + """ + heartbleedVulnerable: Boolean - # The month of August. - AUGUST + """ + Whether or not the scanned server is vulnerable to heartbleed. + """ + robotVulnerable: String - # The month of September. - SEPTEMBER + """ + Whether or not the scanned server is vulnerable to CCS injection. + """ + ccsInjectionVulnerable: Boolean - # The month of October. - OCTOBER + """ + 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 November. - NOVEMBER + """ + 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 month of December. - DECEMBER + """ + The strength of the cipher suite. + """ + strength: String + } - # The last 30 days. - LAST30DAYS + """ + Elliptic curve information. + """ + type EllipticCurve { + """ + The name of the elliptic curve. + """ + name: String + + """ + 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! } - # A field that conforms to a 4 digit integer. - scalar Year + """ + Properties by which web connections can be ordered. + """ + enum WebOrderField { + """ + Order web edges by timestamp. + """ + TIMESTAMP + } - # This object displays the percentages of the category totals. + """ + This object displays the percentages of the category totals. + """ type CategoryPercentages { - # Percentage of messages that are failing all checks. + """ + Percentage of messages that are failing all checks. + """ failPercentage: Float - # Percentage of messages that are passing all checks. + """ + Percentage of messages that are passing all checks. + """ fullPassPercentage: Float - # Percentage of messages that are passing only dkim. + """ + Percentage of messages that are passing only dkim. + """ passDkimOnlyPercentage: Float - # Percentage of messages that are passing only spf. + """ + Percentage of messages that are passing only spf. + """ passSpfOnlyPercentage: Float - # The total amount of messages sent by this domain. + """ + 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 number of domains associated with this organization. + """ + domainCount: Int + + """ + The domains which are associated with this organization. + """ + domains( + """ + Ordering options for domain connections. + """ + orderBy: DomainOrder - # The ordering direction. - direction: OrderDirection! - } + """ + Limit domains to those that belong to an organization that has ownership. + """ + ownership: Boolean - # Properties by which affiliation connections can be ordered. - enum AffiliationOrgOrderField { - # Order affiliations by org acronym. - ORG_ACRONYM + """ + String used to search for domains. + """ + search: String - # Order affiliations by org name. - ORG_NAME + """ + Returns the items in the list that come after the specified cursor. + """ + after: String - # Order affiliations by org slug. - ORG_SLUG + """ + Returns the first n items from the list. + """ + first: Int - # Order affiliations by org zone. - ORG_ZONE + """ + Returns the items in the list that come before the specified cursor. + """ + before: String - # Order affiliations by org sector. - ORG_SECTOR + """ + Returns the last n items from the list. + """ + last: Int + ): DomainConnection + } - # Order affiliations by org country. - ORG_COUNTRY + """ + A connection to a list of items. + """ + type UserConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! - # Order affiliations by org province. - ORG_PROVINCE + """ + A list of edges. + """ + edges: [UserEdge] - # Order affiliations by org city. - ORG_CITY + """ + The total amount of users the user has access to. + """ + totalCount: Int + } - # Order affiliations by org verification. - ORG_VERIFIED + """ + An edge in a connection. + """ + type UserEdge { + """ + The item at the end of the edge + """ + node: SharedUser - # Order affiliations by org summary mail pass count. - ORG_SUMMARY_MAIL_PASS + """ + A cursor for use in pagination + """ + cursor: String! + } - # Order affiliations by org summary mail fail count. - ORG_SUMMARY_MAIL_FAIL + """ + Ordering options for affiliation connections. + """ + input UserOrder { + """ + The field to order affiliations by. + """ + field: UserOrderField! - # Order affiliations by org summary mail total count. - ORG_SUMMARY_MAIL_TOTAL + """ + The ordering direction. + """ + direction: OrderDirection! + } - # Order affiliations by org summary web pass count. - ORG_SUMMARY_WEB_PASS + """ + Properties by which affiliation connections can be ordered. + """ + enum UserOrderField { + """ + Order affiliation edges by username. + """ + USER_USERNAME - # Order affiliations by org summary web fail count. - ORG_SUMMARY_WEB_FAIL + """ + Order affiliation edges by displayName. + """ + USER_DISPLAYNAME - # Order affiliations by org summary web total count. - ORG_SUMMARY_WEB_TOTAL + """ + Order affiliation edges by user verification status. + """ + USER_EMAIL_VALIDATED - # Order affiliations by org domain count. - ORG_DOMAIN_COUNT + """ + 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 - # Whether 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 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 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. + """ + 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. - removeDomain(input: RemoveDomainInput!): RemoveDomainPayload + """ + Mutation to add domain to user's personal myTracker view. + """ + favouriteDomain(input: FavouriteDomainInput!): FavouriteDomainPayload - # 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. + """ + removeDomain(input: RemoveDomainInput!): RemoveDomainPayload - # Mutation allows the modification of domains if domain is updated through out its life-cycle - updateDomain(input: UpdateDomainInput!): UpdateDomainPayload + """ + This mutation allows the removal of unused domains. + """ + removeOrganizationsDomains(input: RemoveOrganizationsDomainsInput!): RemoveOrganizationsDomainsPayload - # This mutation allows the creation of an organization inside the database. - createOrganization( - input: CreateOrganizationInput! - ): CreateOrganizationPayload + """ + This mutation is used to start a subdomain discovery scan on a requested domain. + """ + requestDiscovery(input: RequestDiscoveryInput!): RequestDiscoveryPayload - # This mutation allows the removal of unused organizations. - removeOrganization( - input: RemoveOrganizationInput! - ): RemoveOrganizationPayload + """ + This mutation is used to start a manual scan on a requested domain. + """ + requestScan(input: RequestScanInput!): RequestScanPayload - # Mutation allows the modification of organizations if any changes to the organization may occur. - updateOrganization( - input: UpdateOrganizationInput! - ): UpdateOrganizationPayload + """ + Mutation to remove domain from user's personal myTracker view. + """ + unfavouriteDomain(input: UnfavouriteDomainInput!): UnfavouriteDomainPayload - # Mutation allows the verification of an organization. - verifyOrganization( - input: VerifyOrganizationInput! - ): VerifyOrganizationPayload + """ + Mutation allows the modification of domains if domain is updated through out its life-cycle + """ + updateDomain(input: UpdateDomainInput!): UpdateDomainPayload - # This mutation allows users to give their credentials and retrieve a token that gives them access to restricted content. + """ + 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 - # This mutation allows a user to close their account, or a super admin to close another user's account. - closeAccount(input: CloseAccountInput!): CloseAccountPayload + """ + This mutation allows a super admin to close another user's account. + """ + closeAccountOther(input: CloseAccountOtherInput!): CloseAccountOtherPayload - # This mutation allows users to give their current auth token, and refresh token, and receive a freshly updated auth token. + """ + 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. + """ 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 successful 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 RemoveUserFromOrgPayload { - # \`RemoveUserFromOrgUnion\` returning either a \`RemoveUserFromOrgResult\`, or \`RemoveUserFromOrgError\` object. + """ + 'RemoveUserFromOrgUnion' returning either a 'RemoveUserFromOrgResult', or 'RemoveUserFromOrgError' object. + """ result: RemoveUserFromOrgUnion 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 + """ + 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 of the removal status. + """ + This object is used to inform the user of the removal status. + """ type RemoveUserFromOrgResult { - # Informs the user if the user was successfully removed. + """ + Informs the user if the user was successfully removed. + """ status: String - # The user that was just removed. + """ + The user that was just removed. + """ user: SharedUser } input RemoveUserFromOrgInput { - # The user id of the user to be removed. + """ + The user id of the user to be removed. + """ userId: ID! - # The organization that the user is to be removed from. + """ + The organization that the user is to be removed from. + """ + orgId: ID! + clientMutationId: String + } + + type RequestOrgAffiliationPayload { + """ + 'InviteUserToOrgUnion' returning either a 'InviteUserToOrgResult', or 'InviteUserToOrgError' object. + """ + result: InviteUserToOrgUnion + clientMutationId: String + } + + input RequestOrgAffiliationInput { + """ + The organization you wish to invite the user to. + """ orgId: ID! clientMutationId: String } type TransferOrgOwnershipPayload { - # \`TransferOrgOwnershipUnion\` resolving to either a \`TransferOrgOwnershipResult\` or \`AffiliationError\`. + """ + 'TransferOrgOwnershipUnion' resolving to either a 'TransferOrgOwnershipResult' or 'AffiliationError'. + """ result: TransferOrgOwnershipUnion clientMutationId: 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 + """ + 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 - # This object is used to inform the user that they successful transferred ownership of a given organization. + """ + 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 message confirming the user transferred ownership of the org. + """ status: String } input TransferOrgOwnershipInput { - # Id of the organization the user is looking to transfer ownership of. + """ + 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. + """ + 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 who's role was successfully updated. + """ + Informs the user if the user who's role was successfully updated. + """ status: String - # The user who's role was successfully updated. + """ + 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 CloseAccountPayload { - # \`CloseAccountUnion\` returning either a \`CloseAccountResult\`, or \`CloseAccountError\` object. + 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. + """ + 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. + """ + This object is used to inform the user of the status of closing their account. + """ type CloseAccountResult { - # Status of closing the users account. + """ + Status of closing the users account. + """ status: String } - # This object is used to inform the user if any errors occurred while closing their account. + """ + 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. + """ + Error code to inform user what the issue is related to. + """ code: Int - # Description of the issue encountered. + """ + Description of the issue encountered. + """ description: String } - input CloseAccountInput { - # The user id of a user you want to close the account of. + 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 { @@ -3148,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 } @@ -3178,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 - # Whether 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 } @@ -3320,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 719baf5f49..b201de249d 100644 --- a/frontend/mocking/mocker.js +++ b/frontend/mocking/mocker.js @@ -1,7 +1,8 @@ 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' import { getStringOfDomains } from './helpers/getStringOfDomains' @@ -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) @@ -456,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 }) @@ -477,8 +506,7 @@ const mocks = { refLink, } }, - Selector: () => - 'selector' + faker.datatype.number({ min: 1, max: 9 }), + Selector: () => 'selector' + faker.datatype.number({ min: 1, max: 9 }), SignInError: () => ({ description: 'Mocked sign in error description', }), @@ -529,6 +557,11 @@ 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, } @@ -549,11 +582,7 @@ const edgesToConnection = (store, edges, args) => { 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 allEdges = store.get(resolveInfo.returnType.toString(), resolveInfo.path.key, 'edges') return edgesToConnection(store, allEdges, args) } @@ -572,12 +601,7 @@ const dmarcPhaseSummaryMock = () => { 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 totalDomains = notImplementedTotal + assessTotal + deployTotal + enforceTotal + maintainTotal const notImplementedCategory = { name: 'not implemented', @@ -605,13 +629,7 @@ const dmarcPhaseSummaryMock = () => { percentage: (maintainTotal / totalDomains) * 100, } - const categories = [ - notImplementedCategory, - assessCategory, - deployCategory, - enforceCategory, - maintainCategory, - ] + const categories = [notImplementedCategory, assessCategory, deployCategory, enforceCategory, maintainCategory] return { total: totalDomains, @@ -626,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) @@ -643,6 +658,9 @@ const schemaWithMocks = addMocksToSchema({ findMyOrganizations: (_, args, _context, resolveInfo) => { return getConnectionObject(store, args, resolveInfo) }, + findMyTracker: (_, _args, _context, _resolveInfo) => { + return store.get('MyTrackerResult') + }, dmarcPhaseSummary: (_, _args, _context, _resolveInfo) => { return dmarcPhaseSummaryMock() }, @@ -663,10 +681,7 @@ const schemaWithMocks = addMocksToSchema({ }, Organization: { affiliations: (parent, args, _context, _resolveInfo) => { - const organizationAffiliationEdges = store.get(parent, [ - 'affiliations', - 'edges', - ]) + const organizationAffiliationEdges = store.get(parent, ['affiliations', 'edges']) return edgesToConnection(store, organizationAffiliationEdges, args) }, domains: (parent, args, _context, _resolveInfo) => { @@ -674,14 +689,6 @@ const schemaWithMocks = addMocksToSchema({ return edgesToConnection(store, organizationDomainEdges, args) }, }, - WebScan: { - https: (_, args, _context, resolveInfo) => { - return getConnectionObject(store, args, resolveInfo) - }, - ssl: (_, args, _context, resolveInfo) => { - return getConnectionObject(store, args, resolveInfo) - }, - }, Mutation: { refreshTokens: (_, _args, context, _resolveInfo) => { if (!context.cookies.refresh_token) { @@ -703,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) @@ -714,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', }, } @@ -733,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 { @@ -813,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 { @@ -834,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', }, } @@ -858,7 +825,6 @@ const schemaWithMocks = addMocksToSchema({ const refreshToken = tokenize({ parameters: { userKey: userId, - expPeriod: REFRESH_TOKEN_EXPIRY_SECONDS, }, }) @@ -891,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 2aba2d7c42..3842ef4d76 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,123 +9,148 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@apollo/client": "^3.5.7", - "@babel/runtime": "^7.16.7", - "@chakra-ui/icons": "^1.1.2", - "@chakra-ui/react": "^1.7.5", - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@lingui/react": "^3.13.0", - "@visx/axis": "^2.4.0", - "@visx/event": "^2.1.2", - "@visx/grid": "^2.4.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.2.2", - "@visx/mock-data": "^2.1.2", - "@visx/responsive": "^2.4.1", - "@visx/scale": "^2.2.2", - "@visx/shape": "^2.4.0", - "@visx/tooltip": "^2.2.2", - "body-parser": "^1.19.1", - "d3": "^6.7.0", - "d3-scale": "^3.3.0", - "d3-selection": "^2.0.0", - "dotenv": "^8.6.0", - "express": "^4.17.2", - "fast-deep-equal": "^3.1.3", + "@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.5", + "compression": "^1.8.1", + "d3": "^7.8.0", + "dotenv": "^16.0.1", + "express": "^4.22.2", "formik": "^2.2.9", "framer-motion": "^4.1.17", - "graphql-tag": "^2.12.6", + "intro.js": "^7.2.0", + "intro.js-react": "^1.0.0", "isomorphic-unfetch": "^3.1.0", - "make-plural": "^6.2.2", + "json-2-csv": "^3.17.1", + "lodash-es": "^4.18.1", + "make-plural": "^7.1.0", "prop-types": "^15.8.1", - "react": "^16.14.0", - "react-dom": "^16.14.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", - "react-phone-input-2": "^2.14.0", - "react-router-dom": "^5.3.0", - "react-select": "^4.3.1", - "react-swipe": "^6.0.4", - "react-table": "^7.7.0", - "subscriptions-transport-ws": "^0.11.0", + "react-joyride": "^2.8.2", + "react-phone-input-2": "^2.15.1", + "react-router-dom": "^6.30.4", + "react-table": "^7.8.0", "yup": "^0.32.11" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.7", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-transform-runtime": "^7.16.8", - "@babel/preset-env": "^7.16.8", - "@babel/preset-react": "^7.16.7", - "@graphql-tools/mock": "^8.5.1", - "@graphql-tools/schema": "^7.1.5", - "@hot-loader/react-dom": "^16.14.0", - "@lingui/cli": "^3.13.0", - "@lingui/loader": "^3.13.0", - "@lingui/macro": "^3.13.0", - "@testing-library/jest-dom": "^5.16.1", - "@testing-library/react": "^11.2.7", + "@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": "^2.25.3", - "babel-loader": "^8.2.3", + "apollo-server": "^3.12.0", + "babel-loader": "^8.2.5", "babel-plugin-macros": "^3.1.0", "clean-webpack-plugin": "^3.0.0", + "copy-webpack-plugin": "^14.0.0", "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^24.7.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.3.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-standard": "^4.1.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": "^15.8.0", - "graphql-relay": "^0.9.0", + "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": "^10.0.32", - "jsonwebtoken": "^8.5.1", + "jest-emotion": "^11.0.0", + "jsonwebtoken": "^9.0.0", "mq-polyfill": "^1.1.8", - "nodemon": "^2.0.15", - "prettier": "^2.5.1", + "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.2", - "supertest": "^6.2.1", - "webpack": "^5.66.0", - "webpack-cli": "^4.9.1", + "react-test-renderer": "^18.2.0", + "source-map-loader": "^4.0.0", + "supertest": "^6.2.3", + "webpack": "^5.105.0", + "webpack-cli": "^5.1.4", "webpack-config-utils": "^2.3.1", - "webpack-dev-server": "^3.11.3" + "webpack-dev-server": "^5.2.4" + } + }, + "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.5.7", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.5.7.tgz", - "integrity": "sha512-HSLqTGp3sp/PVIWYLLr5v3fjQSr6Fxg6Z5RQj5Q9ALyseIVudD8GZk1jHplaUblTFMBueXGw3Z6DXObuVAr3tw==", + "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", + "@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.3", + "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.9.4", + "ts-invariant": "^0.10.3", "tslib": "^2.3.0", - "zen-observable-ts": "^1.2.0" + "zen-observable-ts": "^1.2.5" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", - "react": "^16.8.0 || ^17.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 }, @@ -140,17 +165,17 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/@apollo/client/node_modules/zen-observable-ts": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.3.tgz", - "integrity": "sha512-hc/TGiPkAWpByykMwDcem3SdUgA4We+0Qb36bItSuJC9xD0XVBZoFHYoadAomDSNf64CG8Ydj0Qb8Od8BUWz5g==", + "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": { "zen-observable": "0.8.15" } }, "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": { @@ -165,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": { @@ -173,83 +197,158 @@ "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.2", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.2.tgz", - "integrity": "sha512-KxZiw0Us3k1d0YkJDhOpVH5rJ+mBfjXcgoRoCcslbgirjgLotKMzOcx4PZ7YTEvvEROmvG7X3Aon41GvMmyGsw==", + "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/@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, + "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": ">=12.13.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" } }, - "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.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": { - "@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" + "lodash.sortby": "^4.7.0" }, "engines": { - "node": ">=8.5" + "node": ">=12.13.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@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, + "engines": { + "node": ">=12.13.0" }, "peerDependencies": { - "graphql": "0.13.1 - 15" + "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.16.8", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.8.tgz", - "integrity": "sha512-FTKBbxyk5TclXOGmwYyqelqP5IF6hMxaeJskd85jbR5jBfYlwqgwAbJwnixi1ZBbTqKfFuAA95mdmUFeSRwyJA==", + "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", @@ -298,44 +397,48 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dependencies": { - "@babel/highlight": "^7.16.7" + "@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.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "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" @@ -345,106 +448,146 @@ "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.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "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==", "dependencies": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@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/generator/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==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "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==", + "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.16.7" + "@babel/types": "^7.18.6" }, "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==", + "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.16.7", - "@babel/types": "^7.16.7" + "@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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "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.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.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" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.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.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==", + "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-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-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" @@ -454,13 +597,13 @@ } }, "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==", + "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.16.7", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" }, "engines": { "node": ">=6.9.0" @@ -470,15 +613,13 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz", - "integrity": "sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==", + "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-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", + "@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", @@ -489,9 +630,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "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" @@ -512,258 +653,264 @@ "dev": true }, "node_modules/@babel/helper-define-polyfill-provider/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/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dependencies": { - "@babel/types": "^7.16.7" - }, + "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==", + "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/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "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.16.7" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dependencies": { - "@babel/types": "^7.16.7" - }, + "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==", "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==", + "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.16.7" + "@babel/types": "^7.18.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "devOptional": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@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==", + "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.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "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==", + "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.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" + "@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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "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-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-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.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==", + "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==", + "dev": true, "dependencies": { - "@babel/types": "^7.16.7" + "@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.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==", + "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.16.0" + "@babel/types": "^7.18.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "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.16.7" + "@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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "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.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "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.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" + "@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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", + "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.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "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.16.7", - "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.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dependencies": { + "@babel/types": "^7.29.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -772,12 +919,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -787,14 +934,14 @@ } }, "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==", + "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.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7" + "@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" @@ -804,13 +951,14 @@ } }, "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==", + "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.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", + "@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": { @@ -821,13 +969,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -837,13 +985,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@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": { @@ -854,12 +1002,12 @@ } }, "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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -870,12 +1018,12 @@ } }, "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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -886,12 +1034,12 @@ } }, "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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -902,12 +1050,12 @@ } }, "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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -918,12 +1066,12 @@ } }, "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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -934,12 +1082,12 @@ } }, "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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -950,16 +1098,16 @@ } }, "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==", + "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/compat-data": "^7.16.4", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@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.16.7" + "@babel/plugin-transform-parameters": "^7.18.8" }, "engines": { "node": ">=6.9.0" @@ -969,12 +1117,12 @@ } }, "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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -985,13 +1133,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.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": { @@ -1002,13 +1150,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1018,14 +1166,14 @@ } }, "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==", + "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.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@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": { @@ -1036,13 +1184,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=4" @@ -1120,6 +1268,21 @@ "@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": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -1139,11 +1302,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1237,12 +1401,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1252,14 +1416,14 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" + "@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" @@ -1269,12 +1433,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1284,12 +1448,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1299,18 +1463,18 @@ } }, "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", + "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": { @@ -1321,12 +1485,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1336,12 +1500,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1351,13 +1515,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1367,12 +1531,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1382,13 +1546,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1398,12 +1562,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1413,14 +1577,14 @@ } }, "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==", + "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-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@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" @@ -1430,12 +1594,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1445,12 +1609,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1460,13 +1624,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "babel-plugin-dynamic-import-node": "^2.3.3" }, "engines": { @@ -1477,14 +1641,14 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@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": { @@ -1495,16 +1659,15 @@ } }, "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==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "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" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1514,13 +1677,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1530,12 +1693,13 @@ } }, "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==", + "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.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1545,12 +1709,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1560,13 +1724,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-replace-supers": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1576,12 +1740,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1591,12 +1755,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1606,12 +1770,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", - "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1621,16 +1785,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.7.tgz", - "integrity": "sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag==", + "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.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-jsx": "^7.16.7", - "@babel/types": "^7.16.7" + "@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" @@ -1640,12 +1804,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", - "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==", + "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/plugin-transform-react-jsx": "^7.16.7" + "@babel/plugin-transform-react-jsx": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1655,13 +1819,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", - "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1671,12 +1835,13 @@ } }, "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==", + "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" @@ -1686,12 +1851,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1701,16 +1866,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.8.tgz", - "integrity": "sha512-6Kg2XHPFnIarNweZxmzbgYnnWsXxkx9WQUVk2sksBRL80lBC1RAQV3wQagWxdCHiYHqPN+oenwNIuttlYgIbQQ==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", + "@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": { @@ -1721,21 +1886,21 @@ } }, "node_modules/@babel/plugin-transform-runtime/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/@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1745,13 +1910,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1761,12 +1926,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1776,12 +1941,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1791,12 +1956,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1806,12 +1971,12 @@ } }, "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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1821,13 +1986,13 @@ } }, "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==", + "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.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1837,37 +2002,38 @@ } }, "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", + "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-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", @@ -1877,44 +2043,44 @@ "@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/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.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", + "@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": { @@ -1925,9 +2091,9 @@ } }, "node_modules/@babel/preset-env/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" @@ -1950,17 +2116,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", - "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==", + "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.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-react-display-name": "^7.16.7", - "@babel/plugin-transform-react-jsx": "^7.16.7", - "@babel/plugin-transform-react-jsx-development": "^7.16.7", - "@babel/plugin-transform-react-pure-annotations": "^7.16.7" + "@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" @@ -1970,68 +2136,61 @@ } }, "node_modules/@babel/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "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==", "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@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.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "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/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": { @@ -2040,12 +2199,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "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" @@ -2058,2218 +2217,2482 @@ "dev": true }, "node_modules/@chakra-ui/accordion": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-1.4.3.tgz", - "integrity": "sha512-UKpi7xd+hcT/OIDRBRT4vkvVmpym5K9tf3tPyPdwpJcCfCd0D8dvmgZWBM4vCCRPCNfuoyxba5z97witzLCf+g==", + "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.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.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.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-1.3.2.tgz", - "integrity": "sha512-+OMeVeGtydpj6nry0zH7qFDt36zEaxckRnufx1BGiCfWdUg6ahVwKXl8qX93Q8w82od7eAoBKMgGJz7IVL5NPw==", + "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": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-1.2.1.tgz", - "integrity": "sha512-kNS+FiEDTSnwpQUW4dEjZ5745xhkvB0XtmqjY1wpclUSpFfptLZM9QIHPTnBt2bzM9R+idmRRP+WkTt6kyTrLw==", - "dependencies": { - "@chakra-ui/theme-tools": "^1.3.1" - } + "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.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-1.3.3.tgz", - "integrity": "sha512-kc/5L0q1oBSuf1fjLp2DLiV18MSWYdG71311Rx3oZyW2tQcO1I2E+RkkGocAmRD6qkBFvIiBEEBUTfHW6X/uVA==", + "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.1.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-1.3.1.tgz", - "integrity": "sha512-b1IoBmtr5FcP2fn5NRbdOdQo2c866OQ/WhcTcZ6UKae1jjik+36/qWE+X+RKzxC6FLfqo5qayV5zSgsnZym7Pg==", + "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.2.1", - "@chakra-ui/utils": "1.9.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.5.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-1.5.2.tgz", - "integrity": "sha512-1LyZ7o4g+ZF0Qfwr77Fa3qHtCGhghoIPG2lcUWhhPk+8ygHNF0ZOngfsiDD7aoEj0uXsvlgZ/hdiqv0PmMTRzg==", + "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/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/spinner": "1.2.1", - "@chakra-ui/utils": "1.9.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.6.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-1.6.2.tgz", - "integrity": "sha512-eu4MeKWqwFc3zakjMnUi1pGXQ17HzeWbvQHwEZCYgIDk+S9DXopQr8o38zaHzs/MHmlelCzM96IBgTWZXWR/eg==", + "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.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0", - "framer-motion": "3.x || 4.x || 5.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.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-1.2.1.tgz", - "integrity": "sha512-B0CIbKzDMwzG1APeTpW9H2Jl8dkarI1Qstb3hDOy23O+N5TU6lpDdVnXQ7fpFJS6mu5JjFqtkwzGAVZnkkv1rw==", + "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.2.1", - "@chakra-ui/utils": "1.9.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.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-1.2.2.tgz", - "integrity": "sha512-SqeLib0qgMjK3OsO1g5OnAHUmdCC8GMjToNEea7TeSrA44bH9EXVhFTkMMu2PnDVHbQmi4Ee1cuulNJt0UhQ3g==", + "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": "2.0.0", - "@chakra-ui/utils": "1.9.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.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-1.3.3.tgz", - "integrity": "sha512-vEMG9PRtVllAxbJE76bM4fRdo+hRZsPX/9cO+gR1YegYQNeobbV9Ive/MKNYQzdlbQ855Ytn6cPGr2hzy9MdRg==", + "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.7.2", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/utils": "1.9.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.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-1.1.1.tgz", - "integrity": "sha512-ZFbh85pzzZoiSjGnvLUzMB5BoA8Xm6TBMWvMtzLY5xiFGb9/mBeRDH2KFjr1GJzoqleWKkQwvFD6JM0kXcekpg==", - "dependencies": { - "@chakra-ui/utils": "1.9.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.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-1.2.2.tgz", - "integrity": "sha512-EljrsJYHpR5tZ1UczzlZ7gwKZs3ijF2tKCAAmsTpRerPbwGFvrmhfFfLexgbc0vxmeuZYUVeuiClLXQT4lvd5w==", + "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.7.2", - "@chakra-ui/utils": "1.9.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.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz", - "integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==", + "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.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-2.1.1.tgz", - "integrity": "sha512-JasdVaN4MjL7QFo1vMnADy6EtFAlPKT1kTJ1LwMtl9AaF9VFLBsfGxm0L+WQK+3NJMuCSDBXWJB8mV4AQ11Edg==", + "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.2.1" + "@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.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-1.3.2.tgz", - "integrity": "sha512-WY0dq+hQOIyMAamFj2fECNb1AtOwxoddAbZ6k4/epiKNkaRA5ENzgPdV6Gw3t02fDXv95D0J4V4XcpNgfCz6TA==", - "dependencies": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-1.2.1.tgz", - "integrity": "sha512-HYu39nvfaXUrBx+dIDJkFgebNCGEi9oZTfLUKzIJC+zPkmReTDSXV0dzSb/8vCAOq5fph1gFKsdbGy2U98P8GQ==", + "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.9.1", - "react-focus-lock": "2.5.2" + "@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.5.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-1.5.3.tgz", - "integrity": "sha512-+UkOJJYUSGVATeNK/2b+KJx3qRGAtqpD1sR2mqd9DhgmJ/JUGV8q4LVcXIcVs/7BYNqP9B0JQUBIDlHNuTwlJA==", + "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.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.7.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-1.7.2.tgz", - "integrity": "sha512-XJnya9ugAPYUedtQULKaLYBezE9cZMOkDm0MQl7FEuJKZ9ocHD6Pwpwf9Z03R91XQmcNL8gZ1NS0GT9v/xNl3Q==", + "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.2.1", - "@chakra-ui/utils": "1.9.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": "2.0.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-2.0.0.tgz", - "integrity": "sha512-/GuU+xIcOIy9uSUUUCu249ZJB/nLDbjWGkfpoSdBwqT4+ytJrKt+0Ckh3Ub14sz3BJD+Z6IiIt6ySOA9+7lbsA==", + "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.9.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.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-1.1.2.tgz", - "integrity": "sha512-ixrhJhgF6nQOskY4zIsFG8qSiuiUjaKCR+Xmy8MCuGW+/kbztqkzPeDOc0Ypq3JUtH44mU1dP9LjQzxRIviSCA==", + "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": "2.0.0", - "@types/react": "^17.0.15" + "@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.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-1.1.2.tgz", - "integrity": "sha512-bW4PMONYpWRLJ10di7W5BhYUOcCohNLhiTygOmVRMHWfUk9vmxhORlNu0EaSXUKGXiQ35cTVu4Ysv3L/1xYPRQ==", + "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.7.2", - "@chakra-ui/utils": "1.9.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.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-1.3.3.tgz", - "integrity": "sha512-Fsivp608cKdc2tSQvPXczHtyfU6YHLju+EuAs5pjqKC8ZTwPawBRkCvlTGPPd7VwyTHkp08X5qARCxEgjRFbzw==", + "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.5.3", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-1.7.1.tgz", - "integrity": "sha512-88kCHKFOD1JVN9/ErUcO5JFSnZ8SURmxJefaJHV7tNWzphjeFLoYdgXYdT9a+rwk80LNDQMvhN68AkXmPTve7Q==", + "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": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-1.1.1.tgz", - "integrity": "sha512-BSdI5gLIffNRETEp6W18kBNg9tL0ZLLzfWGRnuO9tEbox7NrcgqIeLF8mNKwhDOZz88NKHtUOPVzjAUKW1SryQ==", - "dependencies": { - "@chakra-ui/utils": "1.9.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.2.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-1.2.3.tgz", - "integrity": "sha512-DbStv1VUSBwFj/MNemxRUtoibSoR9ZRcW31UUjtkiIH58cSB5lyEPv9rdD/1HRXpJfSaWQ439Tbuf03gOC4SVQ==", + "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.1.1", - "@chakra-ui/utils": "1.9.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.8.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-1.8.3.tgz", - "integrity": "sha512-TehcqHLKlMdkeXTtCYXzWNa6nbAk1cpjGz6OPl2ua0eisoOKgPolpkreCfYrWV9NNk0BmzZn+Su+z60jbhqggA==", - "dependencies": { - "@chakra-ui/clickable": "1.2.1", - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.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 || 5.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.10.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-1.10.4.tgz", - "integrity": "sha512-Bw7gF/k9/S4d5CMLjrInAg/cPPfSRdQQgI8qQ4prSSMK37sJwA+kalwuKZaKBZeq3J7CS9YGbtf58uEzyCxLug==", - "dependencies": { - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/focus-lock": "1.2.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.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 || 5.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.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-1.3.3.tgz", - "integrity": "sha512-nptvORoG+PvvuURkzh0juSYxj3L9HnWIeNncC/7oXgm2AFGxIcqUk8pUJaUNM2mlbLjJZS19/MWlfzgsYJmJrQ==", - "dependencies": { - "@chakra-ui/counter": "1.2.2", - "@chakra-ui/form-control": "1.5.3", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.7.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-1.7.2.tgz", - "integrity": "sha512-2dGNdU+Xlu0u1OxuelJBKQu7dDKaD5kN9moZMoKpjpXB8Kibh7GM3OO/Z25aMM/E9Kly21qXgHju0Tj/ovBJuw==", + "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.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.11.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-1.11.1.tgz", - "integrity": "sha512-WV1R2L1V6mbpe6/aInr4yFe6rAu7+pT8Od6Hki5KsIv+4QvoEMYsvJ0VOhaURLLD+NaKXFCsfpc9immluia+AA==", - "dependencies": { - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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 || 5.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.4.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-2.4.1.tgz", - "integrity": "sha512-cuwnwXx6RUXZGGynVOGG8fEIiMNBXUCy3UqWQD1eEd8200eWQobgNk4Z0YwzKuSzJwp0Auy+j5iKefi5FSkyog==", + "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.2.1", + "@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.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-1.3.2.tgz", - "integrity": "sha512-dWUCwEZNpPnbR21+eBbEGuvvOcz5AuS+TZ4V0sUdUEI8B3dPUEylJsveZpYjOCS/YAh4xoxbo6I6haRgrfguvw==", + "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.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-1.2.1.tgz", - "integrity": "sha512-213nN8nbODvD/A23vAtg+r3bRKKatWQHafgmLzeznUcxa/+ac0eVurIS8XSYLRkY4EXQ505re3ZkLhDd98a7QA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", "dependencies": { - "@chakra-ui/theme-tools": "1.3.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/react-context": "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/provider": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-1.7.5.tgz", - "integrity": "sha512-kyQxbH/nOB2e3XUj+I0kGhZYSJDs4Jje4K8gTZvOGY4uFMr1iSgzqzZ1OzQis81Xf8vAStAkhQi9SihIK8Unsg==", + "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/css-reset": "1.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/system": "1.9.1", - "@chakra-ui/utils": "1.9.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": { "@emotion/react": "^11.0.0", "@emotion/styled": "^11.0.0", - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "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.4.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-1.4.4.tgz", - "integrity": "sha512-Tz8bl+yhD2pKHUFyNZtP056eawqOUkSA7n2qEebnoH7Zp65B9zwYHkpvL+2nhL5swBwpnGrUpuK3WOqGz4dvXA==", + "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.5.3", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "@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.7.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-1.7.5.tgz", - "integrity": "sha512-SVLBKmTBe/4QiiWe2FEVjVlsp+0fnFWFo3hBC48tf4EjQZ7ed+gFtkTd5WYkZkDA3zuLiRvHrqJremP1FiFYxg==", - "dependencies": { - "@chakra-ui/accordion": "1.4.3", - "@chakra-ui/alert": "1.3.2", - "@chakra-ui/avatar": "1.3.3", - "@chakra-ui/breadcrumb": "1.3.1", - "@chakra-ui/button": "1.5.2", - "@chakra-ui/checkbox": "1.6.2", - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/control-box": "1.1.1", - "@chakra-ui/counter": "1.2.2", - "@chakra-ui/css-reset": "1.1.1", - "@chakra-ui/editable": "1.3.2", - "@chakra-ui/form-control": "1.5.3", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/image": "1.1.2", - "@chakra-ui/input": "1.3.3", - "@chakra-ui/layout": "1.7.1", - "@chakra-ui/live-region": "1.1.1", - "@chakra-ui/media-query": "1.2.3", - "@chakra-ui/menu": "1.8.3", - "@chakra-ui/modal": "1.10.4", - "@chakra-ui/number-input": "1.3.3", - "@chakra-ui/pin-input": "1.7.2", - "@chakra-ui/popover": "1.11.1", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/progress": "1.2.1", - "@chakra-ui/provider": "1.7.5", - "@chakra-ui/radio": "1.4.4", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/select": "1.2.3", - "@chakra-ui/skeleton": "1.2.5", - "@chakra-ui/slider": "1.5.3", - "@chakra-ui/spinner": "1.2.1", - "@chakra-ui/stat": "1.2.2", - "@chakra-ui/switch": "1.3.2", - "@chakra-ui/system": "1.9.1", - "@chakra-ui/table": "1.3.1", - "@chakra-ui/tabs": "1.6.2", - "@chakra-ui/tag": "1.2.2", - "@chakra-ui/textarea": "1.2.3", - "@chakra-ui/theme": "1.12.3", - "@chakra-ui/toast": "1.5.1", - "@chakra-ui/tooltip": "1.4.3", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "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 || 5.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.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-1.1.1.tgz", - "integrity": "sha512-Lgmb0y4kv0ffsGMelAOaYOd4tYZAv4FYWgV86ckGMjmYQWA8drv4v/lHTNltixxWMmBEpjcHALpJuS6yAZYHug==", + "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.9.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.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-1.2.1.tgz", - "integrity": "sha512-bV8FRaXiOgGxOg03iTNin/B02I+tHH9PQtqUTl3U7cJaoI+5AUYhrqXvl1Ya2/R7zxSFrb/gBVDTgbZiVkJ+Dg==", - "dependencies": { - "@chakra-ui/utils": "^1.9.1" - }, + "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.2.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-1.2.3.tgz", - "integrity": "sha512-LGT8z06InjfMTQyWgQUqinucvObkO0bOR87c4swWlAESkS6w+g8PsH9UJ87NT5W+hzCybvqSfRjMHYwC9qUnrg==", + "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.5.3", - "@chakra-ui/utils": "1.9.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.2.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-1.2.5.tgz", - "integrity": "sha512-lZZ2HHOC+44/fS8+Ml0+2twgxogrsRH0Jh7WLJTkDw5UPhMj0mmB7mGEWYm32RiHo4GDeFj9slI+UJWIgBljMQ==", - "dependencies": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/media-query": "1.2.3", - "@chakra-ui/system": "1.9.1", - "@chakra-ui/utils": "1.9.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.5.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-1.5.3.tgz", - "integrity": "sha512-i14b8MYlantiACI4jyjxU5PdX3Nwmz65TdINd7LywrKZu0ahE4GRXCcIGyM48vlXWfdkFhKLqLuF7+EYwLYtHA==", + "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.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-1.2.1.tgz", - "integrity": "sha512-CQsUJNJWWSot1ku5Se41Nz1dXIDhk+/7FIhTbfRHSjtYZnAab3CPMHBkTGqwbJxQ9oHYgk9Rso3cfG+/ra6aTQ==", + "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.9.1", - "@chakra-ui/visually-hidden": "1.1.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/stat": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-1.2.2.tgz", - "integrity": "sha512-0StsPDC56MjzhdlBl0R8wU0uwj9L1tvhQzge/ELSDn4tQDI7VovrxpFzVH0qsj7EZDwZa0BRQaSrstzWvgmJ/Q==", + "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": "2.0.0", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.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/styled-system": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-1.16.0.tgz", - "integrity": "sha512-5vGU4eCixFMXp9u9PT+5724ptzBLP5AcFks88h19TpIevcElCkvAXw2G2CDJ0jJZM59l61nJGHIjn246f3iJUA==", + "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.9.1", - "csstype": "^3.0.9" + "@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.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-1.3.2.tgz", - "integrity": "sha512-aqhAq93DW97qD/KTBiWe2ip64vxSkN/qimMRFdG4xqtS2DxGeicQiv7Eis3NgAq2xIMM9XOoeXutU4kGkha1+w==", + "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.6.2", - "@chakra-ui/utils": "1.9.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.9.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-1.9.1.tgz", - "integrity": "sha512-zO/PIsosJalC58eh0vFeQGTigUpp9iHLFgUQDjs+STdreXGqYZdpRCYxriMt0ZBHNL+N8arHqlzXOlZxSZk1sA==", + "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.3.3", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/styled-system": "1.16.0", - "@chakra-ui/utils": "1.9.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.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-1.3.1.tgz", - "integrity": "sha512-+ia/7zs7AGj01lon301EEx+mK4918yGc0K6e68Kxomex8tnxkwbskFWs6hX+6Kzbj56ZBm99eLlKpo2iGYX0HA==", - "dependencies": { - "@chakra-ui/utils": "1.9.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.6.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-1.6.2.tgz", - "integrity": "sha512-J0OY4sEhZW0jxlj4MkotLiD0/snMk7IsqvDxUB17MxuFRs5ytUssHzCqO7fexH7fSfd54ITNoUIns6Ir2ueCsA==", + "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.2.1", - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-1.2.2.tgz", - "integrity": "sha512-H25y9nEyUAUdwQDND9P4mMXKf1wf9UH4A3DyP237qVKIyYBpa4aCH8eJU4dunh2yIzASB0DWcr7lsul/HAHxmg==", + "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": "2.0.0", - "@chakra-ui/utils": "1.9.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.2.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-1.2.3.tgz", - "integrity": "sha512-yLObhuSnIX11GyjsiEIC2gFTDtUoGSpHUqHRD8nF9BmuePHqFzG3jdkBrFMd09u0Ee6kr6o0tia6g9Wt29TyQQ==", + "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.5.3", - "@chakra-ui/utils": "1.9.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.12.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-1.12.3.tgz", - "integrity": "sha512-aqMGSfQLYOrcWBKaD5GuVYrpI5Zoq4Gt3D9P+drB/CDTRgBLWuJI1rbKOSSSJUK4RNZ0WbakPwz6wXiVS3vVdQ==", - "dependencies": { - "@chakra-ui/anatomy": "1.2.1", - "@chakra-ui/theme-tools": "1.3.1", - "@chakra-ui/utils": "1.9.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.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-1.3.1.tgz", - "integrity": "sha512-D8arJ5uFGuYZrrFGpXqgov8FhsJYWRyar5oBZY5TJR9gsVYBlJ8Ai91pwM/NflCFqzerTOgyt7bNSGQMdZ8ghA==", + "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.9.1", - "@ctrl/tinycolor": "^3.4.0" + "@zag-js/element-size": "0.10.5" }, "peerDependencies": { - "@chakra-ui/system": ">=1.0.0" + "react": ">=18" } }, - "node_modules/@chakra-ui/toast": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-1.5.1.tgz", - "integrity": "sha512-cynOM/mGqf/dPulYOQMcpQnbf8pQWV2SEz1ymW1Nni1hh6zCqW6+pI+YeMxeadJLAwohnRumwzLsRiSL4l+YRQ==", + "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.3.2", - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/theme": "1.12.3", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.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 || 5.x", - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/tooltip": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-1.4.3.tgz", - "integrity": "sha512-d+JsT65LPYFoGEfmqvqa3xDzW1enBdAHns3GYoHnie92pLseMKQYJsITO5SmwNGnEiOHbJ8pkB/hggo+xHKkpg==", - "dependencies": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - }, + "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 || 5.x", - "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/transition": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-1.4.2.tgz", - "integrity": "sha512-S+BNmpErHlntl//uaqv0sJegzMsQms0OnJapeZaRsvZL4s1SVYrR8kMrXigkdpeh4lAUqGsLpQHPKkzaKGbBOw==", + "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.9.1" + "@chakra-ui/utils": "2.0.15" }, "peerDependencies": { - "framer-motion": "3.x || 4.x || 5.x", - "react": ">=16.8.6" + "react": ">=18" } }, - "node_modules/@chakra-ui/utils": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-1.9.1.tgz", - "integrity": "sha512-Tue8JfpzOqeHd8vSqAnX1l/Y3Gg456+BXFP/TH6mCIeqMAMbrvv25vDskds0wlXRjMYdmpqHxCEzkalFrscGHA==", + "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.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-1.1.1.tgz", - "integrity": "sha512-AGK9YBQS2FW/1e5tfivS8VVXn8y2uTyJ9ACOnGiLm9FNdth9pR0fGil9axlcmhZpEYcSRlnCuma3nkqaCjJnAA==", + "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.9.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/@ctrl/tinycolor": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", - "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==", - "engines": { - "node": ">=10" + "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/@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/@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/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/@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/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/@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/@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/@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/select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", "dependencies": { - "@babel/runtime": "^7.7.2", - "cosmiconfig": "^6.0.0", - "resolve": "^1.12.0" + "@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/@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/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": { - "@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" + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "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==", - "engines": { - "node": ">=10" + "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" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@emotion/cache": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", - "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "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": { - "@emotion/memoize": "^0.7.4", - "@emotion/sheet": "^1.1.0", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", - "stylis": "4.0.13" + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", - "optional": true, + "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": { - "@emotion/memoize": "0.7.4" + "@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/@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" - }, - "node_modules/@emotion/react": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz", - "integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@emotion/cache": "^11.7.1", - "@emotion/serialize": "^1.0.2", - "@emotion/sheet": "^1.1.0", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", - "hoist-non-react-statics": "^3.3.1" + "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": { - "@babel/core": "^7.0.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "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==", + "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": { - "@emotion/hash": "^0.8.0", - "@emotion/memoize": "^0.7.4", - "@emotion/unitless": "^0.7.5", - "@emotion/utils": "^1.0.0", - "csstype": "^3.0.2" + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" } }, - "node_modules/@emotion/sheet": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", - "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" - }, - "node_modules/@emotion/styled": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.6.0.tgz", - "integrity": "sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==", + "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": { - "@babel/runtime": "^7.13.10", - "@emotion/babel-plugin": "^11.3.0", - "@emotion/is-prop-valid": "^1.1.1", - "@emotion/serialize": "^1.0.2", - "@emotion/utils": "^1.0.0" + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "@babel/core": "^7.0.0", - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz", - "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==", - "dependencies": { - "@emotion/memoize": "^0.7.4" + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" } }, - "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==" - }, - "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==" - }, - "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==", - "dev": true, + "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": { - "lodash.get": "^4", - "make-error": "^1", - "ts-node": "^9", - "tslib": "^2" - }, - "engines": { - "node": ">=10.0.0" + "@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": { - "cosmiconfig": ">=6" + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18" } }, - "node_modules/@endemolshinegroup/cosmiconfig-typescript-loader/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/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, + "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": { - "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" + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" } }, - "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, + "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": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "@types/lodash": "*" } }, - "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, + "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": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "tslib": "2.4.0" } }, - "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/@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/@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/@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/@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" + "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" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@graphql-tools/merge": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.1.tgz", - "integrity": "sha512-Q240kcUszhXiAYudjuJgNuLgy9CryDP3wp83NOZQezfA6h3ByYKU7xI6DiKrdjyVaGpYN3ppUmdj0uf5GaXzMA==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "^8.5.1", - "tslib": "~2.3.0" + "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": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.5.3.tgz", - "integrity": "sha512-HDNGWFVa8QQkoQB0H1lftvaO1X5xUaUDk1zr1qDe0xN1NL0E/CrQdJ5UKLqOvH4hkqVUPxQsyOoAZFkaH6rLHg==", - "dev": true, + "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": { - "tslib": "~2.3.0" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@graphql-tools/merge/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/@graphql-tools/mock": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.5.1.tgz", - "integrity": "sha512-cwwqGs9Rofev1JdMheAseqM/rw1uw4CYb35vv3Kcv2bbyiPF+490xdlHqFeIazceotMFxC60LlQztwb64rsEnw==", - "dev": true, + "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": { - "@graphql-tools/schema": "^8.3.1", - "@graphql-tools/utils": "^8.6.0", - "fast-json-stable-stringify": "^2.1.0", - "tslib": "~2.3.0" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@graphql-tools/mock/node_modules/@graphql-tools/schema": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.1.tgz", - "integrity": "sha512-3R0AJFe715p4GwF067G5i0KCr/XIdvSfDLvTLEiTDQ8V/hwbOHEKHKWlEBHGRQwkG5lwFQlW1aOn7VnlPERnWQ==", - "dev": true, + "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": { - "@graphql-tools/merge": "^8.2.1", - "@graphql-tools/utils": "^8.5.1", - "tslib": "~2.3.0", - "value-or-promise": "1.0.11" + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "@chakra-ui/styled-system": ">=2.8.0" } }, - "node_modules/@graphql-tools/mock/node_modules/@graphql-tools/utils": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.1.tgz", - "integrity": "sha512-uxcfHCocp4ENoIiovPxUWZEHOnbXqj3ekWc0rm7fUhW93a1xheARNHcNKhwMTR+UKXVJbTFQdGI1Rl5XdyvDBg==", - "dev": true, + "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": { - "tslib": "~2.3.0" + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "@chakra-ui/styled-system": ">=2.0.0" } }, - "node_modules/@graphql-tools/mock/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/@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": ">=12" + "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/@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", - "dev": true, - "dependencies": { - "@graphql-tools/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" + "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": { - "graphql": "^14.0.0 || ^15.0.0" + "@chakra-ui/system": "2.6.2", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=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/@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/@graphql-tools/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", - "dev": true, + "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": { - "@ardatan/aggregate-error": "0.0.6", - "camel-case": "4.1.2", - "tslib": "~2.2.0" + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "framer-motion": ">=4.0.0", + "react": ">=18" } }, - "node_modules/@graphql-tools/utils/node_modules/tslib": { + "node_modules/@chakra-ui/visually-hidden": { "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==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "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/@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/@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": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + }, + "bin": { + "watch": "cli.js" + }, + "engines": { + "node": ">=0.1.95" } }, - "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==", + "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, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, "engines": { - "node": ">=10.10.0" + "node": ">=10.0.0" } }, - "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, + "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": { - "ms": "2.1.2" - }, + "@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==", "engines": { - "node": ">=6.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@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.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", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "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==", + "optional": true + }, + "node_modules/@emotion/react": { + "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": { + "@babel/core": "^7.0.0", + "react": ">=16.8.0" }, "peerDependenciesMeta": { - "supports-color": { + "@babel/core": { + "optional": true + }, + "@types/react": { "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/@emotion/serialize": { + "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.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } }, - "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/@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/@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, + "node_modules/@emotion/sheet": { + "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.10.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.0.tgz", + "integrity": "sha512-V9oaEH6V4KePeQpgUE83i8ht+4Ri3E8Djp/ZPJ4DQlqWhSKITvgzlR3/YQE2hdfP4Jw3qVRkANJz01LLqK9/TA==", "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" + "@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" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0", + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } } }, - "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": ">=6" + "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": { + "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.8.0" } }, - "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/@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.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/utils": { + "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.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": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "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/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": { - "p-locate": "^4.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "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/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": { - "p-limit": "^2.2.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "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/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": ">=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/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": ">=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/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/@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/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": { - "@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": [ + "freebsd" + ], "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/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": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "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/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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "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/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": { - "color-name": "~1.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "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/@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/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": ">=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/@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": { - "has-flag": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "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/@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, - "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" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 10.14.2" + "node": ">=18" } }, - "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/@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": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "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/@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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "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/@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": { - "color-name": "~1.1.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=7.0.0" + "node": ">=18" } }, - "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/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/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/@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/@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, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "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/@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, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "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/@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/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 10.14.2" + "node": ">=18" } }, - "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/@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": { - "@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" - }, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 10.14.2" + "node": ">=18" } }, - "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/@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": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 10.14.2" + "node": ">=18" } }, - "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/@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": { - "@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" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "node-notifier": "^8.0.0" + "node": ">=18" } }, - "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/@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, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "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/@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": { - "color-name": "~1.1.4" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=7.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.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==", + "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, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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/@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": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "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.13.0" - } - }, - "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": "^10.12.0 || >=12.0.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==", + "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": { - "has-flag": "^4.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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/@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": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/@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": ">=0.10.0" + "node": ">= 4" } }, - "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/@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.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, - "dependencies": { - "@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": ">= 10.14.2" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/@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": { - "@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" + "@graphql-tools/utils": "8.10.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/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "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": { - "@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" + "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/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==", + "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": { - "color-convert": "^2.0.1" + "@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": ">=8" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "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==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@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" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "tslib": "^2.4.0" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.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==", + "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/@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==", + "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": ">=8" + "node": ">=12" } }, - "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==", + "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": { + "@graphql-tools/merge": "^9.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, "engines": { - "node": ">=0.10.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.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==", + "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": { - "has-flag": "^4.0.0" + "@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/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "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": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "@graphql-typed-document-node/core": "^3.1.1", + "dset": "^3.1.2", + "tslib": "^2.4.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "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/@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/@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==", + "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-convert": "^2.0.1" + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=8" + "node": ">=10.10.0" } }, - "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==", + "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": { - "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/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==", + "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/@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": { - "color-name": "~1.1.4" + "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": ">=7.0.0" + "node": ">=12" } }, - "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==", + "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": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "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==", + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/@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/@lingui/babel-plugin-extract-messages": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.13.0.tgz", - "integrity": "sha512-akDiMq+CrF3m4ENA3DlEj+XfskZow6SqvrkOUVIStow5kUqcCBow635W7+YAem2TJNxH+CpVgpGV24osiQx+ZQ==", + "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": { - "@babel/generator": "^7.11.6", - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.13.0", - "mkdirp": "^1.0.4" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@lingui/cli": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.13.0.tgz", - "integrity": "sha512-hK/7z+hqxT9CSzUQUQEefurbjmZCJldLG9kbSp8mNgJ+XLAv1mWPve79pYCbtMK7M7vbyU4uG0ncnH+pHKFF/w==", - "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.13.0", - "@lingui/conf": "^3.13.0", - "babel-plugin-macros": "^3.0.1", - "bcp-47": "^1.0.7", - "chalk": "^4.1.0", - "chokidar": "3.5.1", - "cli-table": "0.3.6", - "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" + "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": ">=10.0.0" + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "babel-plugin-macros": "2 || 3", - "typescript": "2 || 3 || 4" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "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/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?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/@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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.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" + "node": ">=8" + } + }, + "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": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": ">=8" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "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/@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/@lingui/cli/node_modules/has-flag": { + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "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/@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/@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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/@lingui/conf": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.13.0.tgz", - "integrity": "sha512-1vl7NEZWMuiM2JCqnvlGmoyqlwB4isSEZrzvKWGAGMRLxMuuKR6PrH1Khgl4x2WRLZxfEysXTe6YR08Ra68irQ==", + "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/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", "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" + "@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" }, "engines": { - "node": ">=10.0.0" + "node": ">= 10.14.2" } }, - "node_modules/@lingui/conf/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==", @@ -4279,15 +4702,12 @@ }, "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==", + "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==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -4295,12 +4715,9 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@lingui/conf/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==", @@ -4312,13 +4729,13 @@ "node": ">=7.0.0" } }, - "node_modules/@lingui/conf/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/@lingui/conf/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==", @@ -4327,7 +4744,7 @@ "node": ">=8" } }, - "node_modules/@lingui/conf/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==", @@ -4339,301 +4756,46 @@ "node": ">=8" } }, - "node_modules/@lingui/core": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.13.0.tgz", - "integrity": "sha512-UDmI8UL59rLmQDDjBK8JFMX0+i3+pncl3fWG+tD2cXNJkN+MEBrhECTQ2lsM1tCk09AfiATglPPXm1e0tLxxOw==", - "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.13.0", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.13.0.tgz", - "integrity": "sha512-MuLwM6vVMYH/ilCGunNGIPTvZU9gtjaohU54/bDCRbDJ5teN46UKqFTYqP6TwVWa+AF6hRMcgUprBcQJi7Iw8Q==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/cli": "^3.13.0", - "@lingui/conf": "^3.13.0", - "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.13.0", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.13.0.tgz", - "integrity": "sha512-TmwAiFnxtutDEKp7KFtUmq5vIfv56zn0FV0ZgrISUcW1liVlRyqW6YnQ7cv4AzsPnkBhO2+O2YVoHY1r5owMvA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.13.0", - "ramda": "^0.27.1" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "babel-plugin-macros": "2 || 3" - } - }, - "node_modules/@lingui/react": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/react/-/react-3.13.0.tgz", - "integrity": "sha512-FS5NqWHh8Ngj1jnF9Yg+lqnIaMT0SjPBzOT6MpiO36tsWNFAdehqM589utvBmaU0eeV+/CyTF02GH6rd4PjMBg==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "@lingui/core": "^3.13.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.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/@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, - "dependencies": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "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==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "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=", - "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=", - "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" - }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, - "node_modules/@reach/alert/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/@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" - }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, - "node_modules/@reach/utils/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/@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" - }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, - "node_modules/@reach/visually-hidden/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/@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.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "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": { - "@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/@testing-library/dom": { - "version": "7.31.2", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", - "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==", + "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": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.6", - "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" + "@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" + "node": ">= 10.14.2" } }, - "node_modules/@testing-library/dom/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==", @@ -4645,7 +4807,7 @@ "node": ">=8" } }, - "node_modules/@testing-library/dom/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==", @@ -4658,7 +4820,7 @@ "node": ">=10" } }, - "node_modules/@testing-library/dom/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==", @@ -4670,13 +4832,13 @@ "node": ">=7.0.0" } }, - "node_modules/@testing-library/dom/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/@testing-library/dom/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==", @@ -4685,41 +4847,115 @@ "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==", + "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": { - "has-flag": "^4.0.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=8" + "bin": { + "rimraf": "bin.js" } }, - "node_modules/@testing-library/jest-dom": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.1.tgz", - "integrity": "sha512-ajUJdfDIuTCadB79ukO+0l8O+QwN0LiSxDaYUTI4LndbbUsGi6rWU1SCexXzBA2NSjlVB9/vbkasQIL3tmPBjw==", + "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": { - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "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" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=8", - "npm": ">=6", - "yarn": ">=1" + "node": ">=8" } }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "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/@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": { + "@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.14.2" + } + }, + "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" + }, + "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.14.2" + }, + "optionalDependencies": { + "node-notifier": "^8.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==", @@ -4731,29 +4967,20 @@ "node": ">=8" } }, - "node_modules/@testing-library/jest-dom/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": ">=6.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==", + "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", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/@testing-library/jest-dom/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==", @@ -4765,13 +4992,13 @@ "node": ">=7.0.0" } }, - "node_modules/@testing-library/jest-dom/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/@testing-library/jest-dom/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==", @@ -4780,6495 +5007,6949 @@ "node": ">=8" } }, - "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/@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/@testing-library/react": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", - "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", + "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.12.5", - "@testing-library/dom": "^7.28.1" - }, "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" + "node": ">=0.10.0" } }, - "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==", + "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": { - "@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" + "has-flag": "^4.0.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": ">=8" } }, - "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, + "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": { - "@babel/runtime": "^7.12.5" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=10", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "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": { - "@types/node": "*" + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "engines": { + "node": ">= 10.14.2" } }, - "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/@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/@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, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "engines": { + "node": ">=0.10.0" } }, - "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/@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": { - "@babel/types": "^7.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": ">= 10.14.2" } }, - "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/@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/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@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.14.2" } }, - "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/@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/types": "^7.3.0" + "@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.14.2" } }, - "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==", + "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": { - "@types/connect": "*", - "@types/node": "*" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "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": { - "@types/node": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@types/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==", - "dev": true - }, - "node_modules/@types/cookies": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.7.tgz", - "integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==", + "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": { - "@types/connect": "*", - "@types/express": "*", - "@types/keygrip": "*", - "@types/node": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "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/@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/@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/@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==", - "dependencies": { - "@types/d3-color": "^1" + "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/@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.1", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz", - "integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA==" + "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/@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==", + "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": { - "@types/d3-time": "^2" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "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==", + "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/d3-path": "^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": ">= 10.14.2" } }, - "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/@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" + } }, - "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==", + "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, "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" } }, - "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/@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": { - "@types/eslint": "*", - "@types/estree": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "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==", + "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/@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/@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, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" + "engines": { + "node": ">=8" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.26", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz", - "integrity": "sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==", + "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": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "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==", - "dev": 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": { - "@types/node": "*" + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" } }, - "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/@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==", + "devOptional": true, + "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, "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "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/@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": { - "@types/node": "*" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" } }, - "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/@types/http-assert": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.3.tgz", - "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==", - "dev": true + "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==" }, - "node_modules/@types/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==", - "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==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "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/@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.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "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/@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, + "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": { - "@types/istanbul-lib-coverage": "*" + "@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": ">=20.0.0" + }, + "peerDependencies": { + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "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/@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": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" + "@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": ">=20.0.0" } }, - "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/@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/@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/@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/@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/@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/@types/koa": { - "version": "2.13.4", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.4.tgz", - "integrity": "sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==", + "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": { - "@types/accepts": "*", - "@types/content-disposition": "*", - "@types/cookies": "*", - "@types/http-assert": "*", - "@types/http-errors": "*", - "@types/keygrip": "*", - "@types/koa-compose": "*", - "@types/node": "*" + "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/@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/@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": { - "@types/koa": "*" + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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/@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, + "license": "MIT" }, - "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==", - "dependencies": { - "@types/lodash": "*" + "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, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" } }, - "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/@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/@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/@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": { + "@babel/runtime": "^7.20.13", + "cosmiconfig": "^8.0.0", + "jest-validate": "^29.4.3", + "jiti": "^2.5.1", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=20.0.0" + } }, - "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/@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": { + "@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/@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==", + "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/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "@types/yargs-parser": "*" } }, - "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/@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==", + "devOptional": true, + "license": "MIT", "dependencies": { - "@types/react": "*" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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, + "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": { - "@types/react": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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/@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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "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, + "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==", + "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": { - "@types/mime": "^1", - "@types/node": "*" + "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/@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/@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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "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/@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/@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/@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": { + "@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/@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, + "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": { - "@types/jest": "*" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "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, + "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": { - "source-map": "^0.6.1" + "@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/@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, + "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": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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/@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/@types/webpack": { - "version": "4.41.21", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", - "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", - "dev": true, + "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": { - "@types/anymatch": "*", - "@types/node": "*", - "@types/tapable": "*", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "source-map": "^0.6.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "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, + "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": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - } - }, - "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, + "@babel/runtime": "^7.20.13", + "@lingui/message-utils": "5.9.3" + }, "engines": { - "node": ">= 8" + "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/@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/@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": ">=0.10.0" + "node": ">=20.0.0" } }, - "node_modules/@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "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": { - "@types/node": "*" + "@babel/runtime": "^7.20.13", + "@lingui/cli": "5.9.3", + "@lingui/conf": "5.9.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "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/@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": { - "@types/yargs-parser": "*" + "@lingui/core": "5.9.3", + "@lingui/react": "5.9.3" + }, + "engines": { + "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/@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==", - "dev": true - }, - "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/@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": { - "@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" + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=20.0.0" } }, - "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/@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": { - "@typescript-eslint/types": "4.5.0", - "@typescript-eslint/visitor-keys": "4.5.0" + "@babel/runtime": "^7.20.13", + "@lingui/core": "5.9.3" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": ">=20.0.0" + }, + "peerDependencies": { + "@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/@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/@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/@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, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "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/@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": { - "@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" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 8" } }, - "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==", + "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": ">= 8" } }, - "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/@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": { - "ms": "2.1.2" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=6.0" + "node": ">= 8" } }, - "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/@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": { - "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" + "@noble/hashes": "^1.1.5" } }, - "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/@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/@peculiar/asn1-cms": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.7.0.tgz", + "integrity": "sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==", "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "@peculiar/asn1-x509-attr": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "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/@peculiar/asn1-cms/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.7.0.tgz", + "integrity": "sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==", "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" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "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/@peculiar/asn1-csr/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==", + "dev": true }, - "node_modules/@visx/axis": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-2.4.0.tgz", - "integrity": "sha512-5QA1Std1HSfBjVObulMW6gi2ZlBg9zuP8jUpgs1LL5tLWritla4T6cheEGLNa6Yuuy/DKzpHJUmNGBtpBGFEGw==", + "node_modules/@peculiar/asn1-ecc": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.7.0.tgz", + "integrity": "sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/group": "2.1.0", - "@visx/point": "2.1.0", - "@visx/scale": "2.2.2", - "@visx/shape": "2.4.0", - "@visx/text": "2.3.0", - "classnames": "^2.3.1", - "prop-types": "^15.6.0" - }, - "peerDependencies": { - "react": "^16.3.0-0 || ^17.0.0-0" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "node_modules/@visx/bounds": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-2.1.2.tgz", - "integrity": "sha512-O80K6PkDH//6xVDP3rSdd+9GNtBUMJKgVXh1ZjW8tAj/rtRq+GiyE17sB3uqV+btNkg0oJiVsFpmoLI50beJDQ==", + "node_modules/@peculiar/asn1-ecc/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.7.0.tgz", + "integrity": "sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==", + "dev": true, "dependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^16.0.0-0 || ^17.0.0-0", - "react-dom": "^16.0.0-0 || ^17.0.0-0" + "@peculiar/asn1-cms": "^2.7.0", + "@peculiar/asn1-pkcs8": "^2.7.0", + "@peculiar/asn1-rsa": "^2.7.0", + "@peculiar/asn1-schema": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "node_modules/@visx/curve": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-2.1.0.tgz", - "integrity": "sha512-9b6JOnx91gmOQiSPhUOxdsvcnW88fgqfTPKoVgQxidMsD/I3wksixtwo8TR/vtEz2aHzzsEEhlv1qK7Y3yaSDw==", + "node_modules/@peculiar/asn1-pfx/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.7.0.tgz", + "integrity": "sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==", + "dev": true, "dependencies": { - "@types/d3-shape": "^1.3.1", - "d3-shape": "^1.0.6" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "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/@peculiar/asn1-pkcs8/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.7.0.tgz", + "integrity": "sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==", + "dev": true, "dependencies": { - "d3-path": "1" + "@peculiar/asn1-cms": "^2.7.0", + "@peculiar/asn1-pfx": "^2.7.0", + "@peculiar/asn1-pkcs8": "^2.7.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "@peculiar/asn1-x509-attr": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "node_modules/@visx/event": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@visx/event/-/event-2.1.2.tgz", - "integrity": "sha512-x3gAQ9DB4zDA6qqGpzlpGacGuOtmzFi/m5Zq7BJ0OJ7PjNfkIazCsznc9epCT/g9IIhwhs+UN/Ijww4YnFHqHw==", + "node_modules/@peculiar/asn1-pkcs9/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.7.0.tgz", + "integrity": "sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/point": "2.1.0" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "node_modules/@visx/grid": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-2.4.0.tgz", - "integrity": "sha512-1tk8XaLeFRQA8BKy/+oP7aqsE99GKzyEYA1w4waga2VI0bDZiSnuZix6cy5PTusZxt9ZzjTmPGkC2RXq9+I5XA==", + "node_modules/@peculiar/asn1-rsa/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz", + "integrity": "sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/curve": "2.1.0", - "@visx/group": "2.1.0", - "@visx/point": "2.1.0", - "@visx/scale": "2.2.2", - "@visx/shape": "2.4.0", - "classnames": "^2.3.1", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "^16.0.0-0 || ^17.0.0-0" + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "node_modules/@visx/group": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@visx/group/-/group-2.1.0.tgz", - "integrity": "sha512-bZKa54yVjGYPZZhzYHLz4AVlidSr4ET9B/xmSa7nnictMJWr7e/IuZThB/bMfDQlgdtvhcfTgs+ZluySc5SBUg==", + "node_modules/@peculiar/asn1-schema/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.7.0.tgz", + "integrity": "sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==", + "dev": true, "dependencies": { - "@types/react": "*", - "classnames": "^2.3.1", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "^16.0.0-0 || ^17.0.0-0" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "node_modules/@visx/legend": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@visx/legend/-/legend-2.2.2.tgz", - "integrity": "sha512-Pf77v3h12P5W2w5Iw1z+hucJVk19GOg+2R3N+O0SODDeaWg3pyN4eJMcs2LdYSfNWGkcejGu4G9Q/UUgjv7ihw==", + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.7.0.tgz", + "integrity": "sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==", + "dev": true, "dependencies": { - "@types/react": "*", - "@visx/group": "2.1.0", - "@visx/scale": "2.2.2", - "classnames": "^2.3.1", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^16.3.0-0 || ^17.0.0-0" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" } }, - "node_modules/@visx/mock-data": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz", - "integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==", + "node_modules/@peculiar/asn1-x509-attr/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==", + "dev": true + }, + "node_modules/@peculiar/asn1-x509/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==", + "dev": true + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, "dependencies": { - "@types/d3-random": "^2.2.0", - "d3-random": "^2.2.2" + "tslib": "^2.8.1" } }, - "node_modules/@visx/point": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.1.0.tgz", - "integrity": "sha512-vVnfI7oqjjttkn05Xi/ooR0UqQRoGf68lyT3SOl0WPHvIQBGNh3XoVUBHDr15/NUkfErgK6TNlfXY763YncPWg==" + "node_modules/@peculiar/utils/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==", + "dev": true }, - "node_modules/@visx/responsive": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-2.4.1.tgz", - "integrity": "sha512-x5zPUmmsdVX/QaeDj9rLg3mGtNuiomBWUvwBo5oQs2KOtAFiviHx0NpLfY09h+p5gvkmGfTM+8gHEtVtIwLXPg==", + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, "dependencies": { - "@types/lodash": "^4.14.172", - "@types/react": "*", - "lodash": "^4.17.21", - "prop-types": "^15.6.1", - "resize-observer-polyfill": "1.5.1" + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" }, - "peerDependencies": { - "react": "^16.0.0-0 || ^17.0.0-0" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@visx/scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-2.2.2.tgz", - "integrity": "sha512-3aDySGUTpe6VykDQmF+g2nz5paFu9iSPTcCOEgkcru0/v5tmGzUdvivy8CkYbr87HN73V/Jc53lGm+kJUQcLBw==", - "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/@peculiar/x509/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==", + "dev": true + }, + "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/@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/@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/@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": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true + }, + "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": { - "d3-color": "1" + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, - "node_modules/@visx/shape": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-2.4.0.tgz", - "integrity": "sha512-D6XdGCgWi0/0ZKJ5iK8W5gILCKdrbDwnR/e7o6n/favLU0o+ntiI4a9PZBZ5bYS0aFNG7r+miGMcWV/AQfODuA==", + "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/@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/@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/@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/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "engines": { + "node": ">=14.0.0" + } + }, + "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/d3-path": "^1.0.8", - "@types/d3-shape": "^1.3.1", - "@types/lodash": "^4.14.172", - "@types/react": "*", - "@visx/curve": "2.1.0", - "@visx/group": "2.1.0", - "@visx/scale": "2.2.2", - "classnames": "^2.3.1", - "d3-path": "^1.0.5", - "d3-shape": "^1.2.0", - "lodash": "^4.17.21", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react": "^16.3.0-0 || ^17.0.0-0" + "type-detect": "4.0.8" } }, - "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/@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": { - "d3-path": "1" + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@visx/text": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.3.0.tgz", - "integrity": "sha512-5mEcmzWZqbziz6Azv+H6BWrnUbnpygAw4SNzBqF+YiGik5gR/USaBRAs9aKjnoUg6qFe3NZWFOcGpR3BzDR/bQ==", + "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/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" + "@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" }, - "peerDependencies": { - "react": "^16.3.0-0 || ^17.0.0-0" + "engines": { + "node": ">=12" } }, - "node_modules/@visx/tooltip": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-2.2.2.tgz", - "integrity": "sha512-wI0t+hE+gp7euuqI8likKBDsx70I+WJ6UJ9YCra8/LyyuVSbOobnLZlqQw1QhIYbdmnDReIckZ0dq3sa6z2qbw==", + "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/react": "*", - "@visx/bounds": "2.1.2", - "classnames": "^2.3.1", - "prop-types": "^15.5.10", - "react-use-measure": "^2.0.4" + "color-convert": "^2.0.1" }, - "peerDependencies": { - "react": "^16.8.0-0 || ^17.0.0-0", - "react-dom": "^16.8.0-0 || ^17.0.0-0" + "engines": { + "node": ">=8" } }, - "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==", + "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": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" } }, - "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 - }, - "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 - }, - "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 - }, - "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==", + "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": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "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==", + "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/@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==", + "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": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "engines": { + "node": ">=8" } }, - "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==", + "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": { - "@xtuc/ieee754": "^1.2.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/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "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, - "dependencies": { - "@xtuc/long": "4.2.2" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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==", + "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/@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==", + "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": { - "@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" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "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==", + "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": { - "@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" + "@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/@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==", + "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": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "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==", + "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": { - "@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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" } }, - "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==", + "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": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@webpack-cli/configtest": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz", - "integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==", + "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==", "dev": true, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "engines": { + "node": ">=8" } }, - "node_modules/@webpack-cli/info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz", - "integrity": "sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw==", + "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/@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": { - "envinfo": "^7.7.3" + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=12" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "react": "^18.0.0", + "react-dom": "^18.0.0" } }, - "node_modules/@webpack-cli/serve": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz", - "integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==", + "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": { + "@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": { - "webpack-cli": "4.x.x" + "react": ">=16.9.0", + "react-dom": ">=16.9.0", + "react-test-renderer": ">=16.9.0" }, "peerDependenciesMeta": { - "webpack-dev-server": { + "react-dom": { + "optional": true + }, + "react-test-renderer": { "optional": 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==", + "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": { - "tslib": "^2.3.0" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=8" + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" } }, - "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/@wry/equality": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.2.tgz", - "integrity": "sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA==", - "dependencies": { - "tslib": "^2.3.0" - }, + "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": ">=8" + "node": ">= 6" } }, - "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==", + "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": { - "tslib": "^2.3.0" - }, - "engines": { - "node": ">=8" + "@types/node": "*" } }, - "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/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "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/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "node_modules/@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/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "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": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "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, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "@babel/types": "^7.0.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==", + "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": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "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==", + "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, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "@babel/types": "^7.3.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "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, - "peerDependencies": { - "acorn": "^8" + "dependencies": { + "@types/connect": "*", + "@types/node": "*" } }, - "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==", + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "@types/node": "*" } }, - "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==", + "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, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "@types/node": "*" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "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" + "@types/express-serve-static-core": "*", + "@types/node": "*" } }, - "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==", + "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/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/@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/@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/ansi-align": { + "node_modules/@types/d3-format": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, + "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": { - "string-width": "^4.1.0" + "@types/geojson": "*" } }, - "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/@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": { + "@types/d3-path": "^1" } }, - "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==", + "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, "dependencies": { - "type-fest": "^0.11.0" - }, - "engines": { - "node": ">=8" + "@types/estree": "*", + "@types/json-schema": "*" } }, - "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==", + "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, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" } }, - "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==", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" } }, - "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/@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": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "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/@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, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "@types/minimatch": "*", + "@types/node": "*" } }, - "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.", + "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": { - "apollo-server-env": "^3.1.0", - "apollo-server-plugin-base": "^0.13.0" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@types/node": "*" } }, - "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==", + "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/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true + }, + "node_modules/@types/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": { - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0" - }, - "engines": { - "node": ">=6" + "@types/node": "*" } }, - "node_modules/apollo-graphql": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.5.tgz", - "integrity": "sha512-RGt5k2JeBqrmnwRM0VOgWFiGKlGJMfmiif/4JvdaEqhMJ+xqe/9cfDYzXfn33ke2eWixsAbjEbRfy8XbaN9nTw==", - "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==", + "devOptional": 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==", + "devOptional": true, "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" + "@types/istanbul-lib-coverage": "*" } }, - "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/@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": { - "apollo-utilities": "^1.3.0", - "ts-invariant": "^0.4.0", - "tslib": "^1.9.3", - "zen-observable-ts": "^0.8.21" - }, - "peerDependencies": { - "graphql": "^0.11.3 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@types/istanbul-lib-report": "*" } }, - "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==", + "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": { - "tslib": "^1.9.3" + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" } }, - "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, - "dependencies": { - "@apollo/protobufjs": "1.2.2" - } + "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.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "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/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/apollo-server": { - "version": "2.25.3", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.25.3.tgz", - "integrity": "sha512-+eUY2//DLkU7RkJLn6CTl1P89/ZMHuUQnWqv8La2iJ2hLT7Me+nMx+hgHl3LqlT/qDstQ8qA45T85FuCayplmQ==", - "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": { - "apollo-server-core": "^2.25.3", - "apollo-server-express": "^2.25.3", - "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/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" } }, - "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/@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": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=6" + "@types/react": "*" } }, - "node_modules/apollo-server-core": { - "version": "2.25.3", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.25.3.tgz", - "integrity": "sha512-Midow3uZoJ9TjFNeCNSiWElTVZlvmB7G7tG6PPoxIR9Px90/v16Q6EzunDIO0rTJHRC3+yCwZkwtf8w2AcP0sA==", + "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": { - "@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" + "@types/react": "*" } }, - "node_modules/apollo-server-core/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/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "dev": true }, - "node_modules/apollo-server-core/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==", - "dev": true, - "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/@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/apollo-server-core/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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "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/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==", + "node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "dependencies": { - "node-fetch": "^2.6.1", - "util.promisify": "^1.0.0" - }, - "engines": { - "node": ">=6" + "@types/mime": "^1", + "@types/node": "*" } }, - "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==", + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "dependencies": { + "@types/express": "*" } }, - "node_modules/apollo-server-express": { - "version": "2.25.3", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.25.3.tgz", - "integrity": "sha512-tTFYn0oKH2qqLwVj7Ez2+MiKleXACODiGh5IxsB7VuYCPMAi9Yl8iUSlwTjQUvgCWfReZjnf0vFL2k5YhDlrtQ==", + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "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.3", - "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" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" } }, - "node_modules/apollo-server-express/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/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "dependencies": { - "@types/connect": "*", "@types/node": "*" } }, - "node_modules/apollo-server-express/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/@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/apollo-server-express/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==", - "dev": true, - "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/@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/apollo-server-express/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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "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/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==", + "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": { - "apollo-server-types": "^0.9.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@types/jest": "*" } }, - "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==", + "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": { - "apollo-reporting-protobuf": "^0.8.0", - "apollo-server-caching": "^0.7.0", - "apollo-server-env": "^3.1.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "source-map": "^0.6.1" } }, - "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/@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, - "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" - } - }, - "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==", - "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" - } - }, - "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==", - "dev": true, - "dependencies": { - "tslib": "^1.9.3" + "node": ">=0.10.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/@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": { - "tslib": "^1.9.3" + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" } }, - "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==", + "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": { - "sprintf-js": "~1.0.2" - } - }, - "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==", - "dependencies": { - "tslib": "^1.0.0" - }, - "engines": { - "node": ">=8.5.0" + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" } }, - "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/@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, - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, "engines": { - "node": ">=6.0" + "node": ">= 8" } }, - "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/@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, "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==", + "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, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@types/node": "*" } }, - "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/@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, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@types/yargs-parser": "*" } }, - "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/@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/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" + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { - "node": ">= 0.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "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/@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": { - "array-uniq": "^1.0.1" - }, "engines": { - "node": ">=0.10.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "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=", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, "engines": { - "node": ">=0.10.0" + "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/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "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": ">=0.10.0" + "node": ">=8" } }, - "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==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "ms": "2.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/array.prototype.flatmap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", - "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", + "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": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "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": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/@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/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "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": { - "safer-buffer": "~2.1.0" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, "engines": { - "node": ">=0.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/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "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": ">=0.10.0" + "node": ">=10" } }, - "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==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "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/@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, - "dependencies": { - "lodash": "^4.17.14" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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==", - "dev": true + "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": { + "@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" + }, + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" + } }, - "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, + "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": { - "retry": "0.13.1" + "@types/d3-color": "*" } }, - "node_modules/async-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" + "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": { + "@types/d3-time": "*" } }, - "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/@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/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/@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": { + "@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/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" + "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": ">= 4.5.0" + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.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=", - "dev": true, - "engines": { - "node": "*" + "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": { + "@types/react": "*", + "@types/react-dom": "*", + "prop-types": "^15.5.10" + }, + "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/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "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==", - "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": { - "@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" + "@types/d3-shape": "^1.3.1", + "d3-shape": "^1.0.6" } }, - "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, + "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": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "@types/react": "*", + "@visx/point": "2.17.0" } }, - "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, + "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-styles": "^4.1.0", - "supports-color": "^7.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": ">=10" + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "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, + "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": { - "color-name": "~1.1.4" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.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/@visx/gradient": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/gradient/-/gradient-3.3.0.tgz", + "integrity": "sha512-t3vqukahDQsJ64/fcm85woFm2XPpSPMBz92gFvaY4J8EJY3e6rFOg382v5Dm17fgNsLRKJA0Vqo7mUtDe2pWOw==", + "dependencies": { + "@types/react": "*", + "prop-types": "^15.5.7" + }, + "peerDependencies": { + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "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, + "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": { - "has-flag": "^4.0.0" + "@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/babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", - "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": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "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==", - "dev": true, + "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/@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": { - "minimist": "^1.2.0" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, - "bin": { - "json5": "lib/cli.js" + "peerDependencies": { + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-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==", - "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": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@types/react": "*", + "@visx/group": "2.17.0", + "@visx/scale": "2.17.0", + "classnames": "^2.3.1", + "prop-types": "^15.5.10" }, - "engines": { - "node": ">=4.0.0" + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "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, + "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": { - "object.assign": "^4.1.0" + "@types/d3-color": "*" } }, - "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, + "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": { - "@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" + "@types/d3-time": "*" } }, - "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, + "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": { - "@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.14.2" + "@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/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, + "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": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" + "@types/d3-random": "^2.2.0", + "d3-random": "^2.2.2" } }, - "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, + "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/@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": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.0", - "semver": "^6.1.1" + "@juggle/resize-observer": "^3.3.1", + "@types/lodash": "^4.14.172", + "@types/react": "*", + "lodash": "^4.17.21", + "prop-types": "^15.6.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, - "node_modules/babel-plugin-polyfill-corejs2/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/@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/babel-plugin-polyfill-corejs3": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.0.tgz", - "integrity": "sha512-Hcrgnmkf+4JTj73GbK3bBhlVPiLL47owUAnoJIf69Hakl3q+KfodbDXiZWGMM7iqCZTxCG3Z2VRfPNYES4rXqQ==", - "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": { - "@babel/helper-define-polyfill-provider": "^0.3.0", - "core-js-compat": "^3.20.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" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.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, + "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": { - "@babel/helper-define-polyfill-provider": "^0.3.0" + "@types/react": "*", + "classnames": "^2.3.1", + "prop-types": "^15.6.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.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==", - "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": { - "@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" + "@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": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.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==", - "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": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" + "@types/react": "*", + "@visx/bounds": "2.17.0", + "classnames": "^2.3.1", + "prop-types": "^15.5.10", + "react-use-measure": "^2.0.4" }, - "engines": { - "node": ">= 10.14.2" + "peerDependencies": { + "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/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.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "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/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, + "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": { - "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" + "@types/d3-color": "*" } }, - "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, + "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": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "@types/d3-time": "*" } }, - "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, + "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": { - "kind-of": "^6.0.0" + "internmap": "1 - 2" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "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, + "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": { - "kind-of": "^6.0.0" + "d3-array": "2 - 3" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "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/@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, "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "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==", + "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/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "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/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" - } + "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/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "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": { - "tweetnacl": "^0.14.3" + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, - "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/@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, - "engines": { - "node": "*" + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "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/@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, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@xtuc/ieee754": "^1.2.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/@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": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "@xtuc/long": "4.2.2" } }, - "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==", + "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/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", - "dependencies": { - "bytes": "3.1.1", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "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": { - "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" + "@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/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==", - "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/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "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": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@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/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==", + "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": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@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/boxen/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@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/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==", + "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, "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" } }, - "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==", + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, - "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==", + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, - "node_modules/boxen/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==", + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14.15.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/boxen/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, + "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": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "tslib": "^2.3.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "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/@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/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "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": { - "fill-range": "^7.0.1" + "tslib": "^2.3.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/@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/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", - "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" + "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": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "node": ">=8" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } + "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/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "@zag-js/dom-query": "0.16.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==", + "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/buffer-indexof": { + "node_modules/abbrev": { "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==", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, - "node_modules/busboy": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", - "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", - "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": { - "dicer": "0.3.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=4.5.0" + "node": ">= 0.6" } }, - "node_modules/bytes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">= 0.8" + "node": ">=0.4.0" } }, - "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/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": { - "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" + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" } }, - "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/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, - "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" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=8" + "node": ">=0.4.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/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": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10.13.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "acorn": "^8.14.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/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, - "engines": { - "node": ">=8" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.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==", + "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, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "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" + "node": ">= 6.0.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==", + "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": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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/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/camelcase": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", - "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", + "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, - "engines": { - "node": ">=10" + "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": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001298", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001298.tgz", - "integrity": "sha512-AcKqikjMLlvghZL/vfTHorlQsLDhGRalYf1+GmWCf5SCMziSGjRYQW/JEksj14NaYHIR6KIhrFAy0HV5C25UzQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "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": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "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/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": { - "rsvp": "^4.8.4" + "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.* || 8.* || >= 10.*" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "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/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } + "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/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==", + "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/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==", + "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": { - "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" + "type-fest": "^0.11.0" }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" + "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==", + "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/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/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/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": { - "binary-extensions": "^2.0.0" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/chokidar/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==", + "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": { - "picomatch": "^2.2.1" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=8.10.0" + "node": ">= 8" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "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": { - "tslib": "^1.9.0" + "@apollo/utils.keyvaluecache": "^1.0.1", + "apollo-server-env": "^4.2.1" }, "engines": { - "node": ">=6.0" + "node": ">=12.0" } }, - "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/cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "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==", + "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": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" + "@apollo/protobufjs": "1.2.6" } }, - "node_modules/class-utils/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=", + "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": { - "is-descriptor": "^0.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" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "apollo-pbjs": "bin/pbjs", + "apollo-pbts": "bin/pbts" } }, - "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/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/clean-css": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", - "integrity": "sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w==", + "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": { - "source-map": "~0.6.0" + "@types/express": "4.17.14", + "apollo-server-core": "^3.12.0", + "apollo-server-express": "^3.12.0", + "express": "^4.17.1" }, - "engines": { - "node": ">= 10.0" + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/clean-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, + "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": ">=0.10.0" + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/clean-webpack-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", + "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": { - "@types/webpack": "^4.4.31", - "del": "^4.1.1" + "@graphql-tools/utils": "8.9.0", + "tslib": "^2.4.0" }, - "engines": { - "node": ">=8.9.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.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==", + "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, - "engines": { - "node": ">=6" + "dependencies": { + "@graphql-tools/merge": "8.3.1", + "@graphql-tools/utils": "8.9.0", + "tslib": "^2.4.0", + "value-or-promise": "1.0.11" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "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==", + "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": { - "restore-cursor": "^3.1.0" + "tslib": "^2.4.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/cli-spinners": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", - "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==", + "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/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, - "engines": { - "node": ">=6" + "bin": { + "uuid": "dist/bin/uuid" } }, - "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==", + "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": { - "colors": "1.0.3" - }, "engines": { - "node": ">= 0.2.0" + "node": ">=12" } }, - "node_modules/cli-width": { + "node_modules/apollo-server-core/node_modules/whatwg-mimetype": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true, "engines": { - "node": ">= 10" + "node": ">=12" } }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "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": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=12.0" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "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": ">=0.8" + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "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==", + "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": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.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": ">=6" + "node": ">=12.0" + }, + "peerDependencies": { + "express": "^4.17.1", + "graphql": "^15.3.0 || ^16.0.0" } }, - "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=", + "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": { - "mimic-response": "^1.0.0" + "apollo-server-types": "^3.8.0" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.0" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "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": { + "@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": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node": ">=12.0" + }, + "peerDependencies": { + "graphql": "^15.3.0 || ^16.0.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=", + "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": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" + "sprintf-js": "~1.0.2" } }, - "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==", + "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": { - "color-name": "1.1.3" + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/colorette": { - "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/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/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "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": ">=0.1.90" + "node": ">=6.0" } }, - "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==", + "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, - "dependencies": { - "delayed-stream": "~1.0.0" - }, "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "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, "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "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/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/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/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "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": { - "mime-db": ">= 1.43.0 < 2" + "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.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "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": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" + "array-uniq": "^1.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=0.10.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=", + "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.8" + "node": ">=0.10.0" } }, - "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==" - }, - "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/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/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "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": { - "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" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, "dependencies": { - "safe-buffer": "5.2.1" + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" }, "engines": { - "node": ">= 0.6" + "node": ">=12.0.0" } }, - "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/asn1js/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==", + "dev": true }, - "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==", + "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, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dependencies": { - "safe-buffer": "~5.1.1" + "node": ">=0.10.0" } }, - "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "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": ">= 0.6" + "node": ">=8" } }, - "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/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/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "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/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "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": ">=0.10.0" - } - }, - "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==", - "dependencies": { - "toggle-selection": "^1.0.6" + "node": ">= 4.5.0" } }, - "node_modules/core-js-compat": { - "version": "3.20.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.2.tgz", - "integrity": "sha512-qZEzVQ+5Qh6cROaTPFLNS4lkvQ6mBzE3R6A6EEpssj7Zr2egMHgsy4XapdifqJDGC9CBiNv7s+ejI96rLNQFdg==", + "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": { - "browserslist": "^4.19.1", - "semver": "7.0.0" + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://github.com/sponsors/ljharb" } }, - "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/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, - "bin": { - "semver": "bin/semver.js" + "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/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/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, - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "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": { - "object-assign": "^4", - "vary": "^1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10" } }, - "node_modules/cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "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": { - "@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" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10" + "node": ">=7.0.0" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "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/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "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, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, "engines": { - "node": ">= 8" + "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==", + "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/css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "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": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" } }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "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": { - "tiny-invariant": "^1.0.6" + "object.assign": "^4.1.0" } }, - "node_modules/css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "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": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "@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" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": ">=8" } }, - "node_modules/css-what": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", - "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", + "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, - "engines": { - "node": ">= 6" + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": ">= 10.14.2" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "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, + "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": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10", + "npm": ">=6" } }, - "node_modules/cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=", - "dev": true - }, - "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==", + "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": { - "cssom": "~0.3.6" + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", + "semver": "^6.1.1" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" - }, - "node_modules/d3": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-6.7.0.tgz", - "integrity": "sha512-hNHRhe+yCDLUG6Q2LwvR/WdNFPOJQ5VWqsJcwIYVeI401+d2/rrCjxSXkiAdIlpx7/73eApFB4Olsmh3YN7a6g==", - "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" + "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/d3-array": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.7.1.tgz", - "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==" - }, - "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==", + "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": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "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": { - "d3-path": "1 - 2" + "@babel/helper-define-polyfill-provider": "^0.3.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==" - }, - "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==", + "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": { - "d3-array": "2" + "@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/d3-delaunay": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", - "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "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": { - "delaunator": "4" + "babel-plugin-jest-hoist": "^26.6.2", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": ">= 10.14.2" } }, - "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==" + "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/d3-drag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz", - "integrity": "sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==", + "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": { - "d3-dispatch": "1 - 2", - "d3-selection": "2" + "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/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==", + "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": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" + "is-descriptor": "^1.0.0" }, - "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" + "engines": { + "node": ">=0.10.0" } }, - "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==" - }, - "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==" - }, - "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==", + "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": { - "d3-dsv": "1 - 2" + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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": { - "d3-dispatch": "1 - 2", - "d3-quadtree": "1 - 2", - "d3-timer": "1 - 2" + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "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==" - }, - "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==", + "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, "dependencies": { - "d3-array": ">=2.5" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "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/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/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==", - "dependencies": { - "d3-color": "1 - 2" + "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/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "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==" - }, - "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==" + "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/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/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/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==", - "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" + "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/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==", + "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": { - "d3-color": "1 - 2", - "d3-interpolate": "1 - 2" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "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==" + "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/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==", + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "dependencies": { - "d3-path": "1 - 2" + "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.15.1", + "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/d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", - "dependencies": { - "d3-array": "2" + "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": ">= 0.8" } }, - "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==", + "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": { - "d3-time": "1 - 2" + "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/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==" + "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/d3-transition": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-2.0.0.tgz", - "integrity": "sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==", - "dependencies": { - "d3-color": "1 - 2", - "d3-dispatch": "1 - 2", - "d3-ease": "1 - 2", - "d3-interpolate": "1 - 2", - "d3-timer": "1 - 2" + "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": ">= 0.8" } }, - "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==", + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, "dependencies": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, - "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-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + "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/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "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": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "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==", + "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": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "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==", - "dev": true, + "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": ">=0.11" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "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": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "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": { - "ms": "2.0.0" + "node-int64": "^0.4.0" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "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/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, - "engines": { - "node": ">=0.10" - } + "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/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, "dependencies": { - "mimic-response": "^1.0.0" + "run-applescript": "^7.0.0" }, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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/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==", + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", "dev": true, "engines": { - "node": ">=4.0.0" + "node": ">=6.0.0" } }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "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/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "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": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" + "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": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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": { - "clone": "^1.0.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "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, + "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": { - "object-keys": "^1.0.12" + "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/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, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, + "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": ">=0.10.0" + "node": ">=6" } }, - "node_modules/define-property/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/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": { - "kind-of": "^6.0.0" - }, + "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": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-property/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/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": { - "kind-of": "^6.0.0" + "rsvp": "^4.8.4" }, "engines": { - "node": ">=0.10.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/define-property/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/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": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "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, - "dependencies": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "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==" - }, - "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=", + "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": ">=0.4.0" + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" } }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "node_modules/chokidar/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": ">= 0.6" + "node": ">=8.10.0" } }, - "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=" - }, - "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==", + "node_modules/chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, "engines": { - "node": ">=8" + "node": ">=6.0" } }, - "node_modules/detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "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/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + "node_modules/cjs-module-lexer": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", + "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", + "dev": true }, - "node_modules/dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "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, "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" + "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/dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "node_modules/class-utils/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, "dependencies": { - "streamsearch": "0.1.2" + "is-descriptor": "^0.1.0" }, "engines": { - "node": ">=4.5.0" + "node": ">=0.10.0" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "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/clean-css": { + "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": ">=0.3.1" + "node": ">= 10.0" } }, - "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "node_modules/clean-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": ">= 10.14.2" + "node": ">=0.10.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==", + "node_modules/clean-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "@types/webpack": "^4.4.31", + "del": "^4.1.1" }, "engines": { - "node": ">=8" + "node": ">=8.9.0" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "node_modules/dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "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": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" } }, - "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=", + "node_modules/cli-spinners": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", + "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==", "dev": true, - "dependencies": { - "buffer-indexof": "^1.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "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": { - "esutils": "^2.0.2" + "colors": "1.0.3" }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.2.0" } }, - "node_modules/dom-accessibility-api": { - "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": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "dependencies": { - "utila": "~0.4" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "node_modules/dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "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" + "engines": { + "node": ">=0.8" } }, - "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==", - "dev": true - }, - "node_modules/domelementtype": { - "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==", + "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": { - "webidl-conversions": "^5.0.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "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==", + "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": { - "node": ">=8" + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/domhandler": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", - "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", + "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, "dependencies": { - "domelementtype": "^2.2.0" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" }, "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "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": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "color-name": "1.1.3" } }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "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/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" + }, + "node_modules/colorette": { + "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/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "engines": { + "node": ">=0.1.90" } }, - "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/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "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": { - "is-obj": "^2.0.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "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": ">=10" + "node": ">=14" } }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "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/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, + "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/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" } }, - "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==", - "dev": true, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dependencies": { - "safe-buffer": "^5.0.1" + "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/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.4.41", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.41.tgz", - "integrity": "sha512-VQEXEJc+8rJIva85H8EPtB5Ux9g8TzkNGBanqphM9ZWMZ34elueKJ+5g+BPhz3Lk8gkujfQRcIZ+fpA0btUIuw==" - }, - "node_modules/emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "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": ">=10" + "node": ">= 0.6" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/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": "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", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "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==", + "node_modules/connect-history-api-fallback": { + "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": ">= 4" + "node": ">=0.8" } }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, - "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/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/enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { - "node": ">=10.13.0" + "node": ">= 0.6" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" + "safe-buffer": "~5.1.1" } }, - "node_modules/entities": { - "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/cookie": { + "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" } }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "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/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" + "toggle-selection": "^1.0.6" } }, - "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==", + "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": { - "is-arrayish": "^0.2.1" + "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/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "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==", + "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": { - "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" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": 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==", + "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, "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "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==", - "engines": { - "node": ">=6" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "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==", + "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, + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "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=", - "engines": { - "node": ">=0.8.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/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "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": { - "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" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">=6.0" + "node": ">= 10.13.0" }, - "optionalDependencies": { - "source-map": "~0.6.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "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==", + "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": ">=4.0" + "node": ">=20.0.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=", + "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": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "browserslist": "^4.22.2" }, - "engines": { - "node": ">= 0.8.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "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" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.10" } }, - "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, + "node_modules/cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "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": ">= 0.8.0" + "node": ">=10" } }, - "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==", + "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, - "optional": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "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=", + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/css-select": { + "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": { - "prelude-ls": "~1.1.2" + "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": "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": ">= 0.8.0" + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "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==", + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "dev": true + }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", + "dev": true + }, + "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": { - "@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" + "cssom": "~0.3.6" }, - "bin": { - "eslint": "bin/eslint.js" + "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": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/d3": { + "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": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "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/d3-array": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.7.1.tgz", + "integrity": "sha512-dYWhEvg1L2+osFsSqNHpXaPQNugLT4JfyvbLE046I2PDcgYGFYc0w24GSJwbmcjjZYOPC3PNP2S782bWUM967Q==" }, - "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/d3-axis": { + "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/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, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" } }, - "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, + "node_modules/d3-chord": { + "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": { - "ms": "^2.1.1" + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" } }, - "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/d3-color": { + "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/eslint-import-resolver-node/node_modules/resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", - "dev": true, + "node_modules/d3-contour": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", "dependencies": { - "is-core-module": "^2.8.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "d3-array": "^3.2.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=12" } }, - "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, + "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": { - "debug": "^3.2.7", - "find-up": "^2.1.0" + "internmap": "1 - 2" }, "engines": { - "node": ">=4" + "node": ">=12" } }, - "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, + "node_modules/d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", "dependencies": { - "ms": "^2.1.1" + "delaunator": "5" + }, + "engines": { + "node": ">=12" } }, - "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" - }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", "engines": { - "node": ">=4" + "node": ">=12" } }, - "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, + "node_modules/d3-drag": { + "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": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "d3-dispatch": "1 - 3", + "d3-selection": "3" }, "engines": { - "node": ">=4" + "node": ">=12" } }, - "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", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "dependencies": { - "p-try": "^1.0.0" + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "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": ">=4" + "node": ">=12" } }, - "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, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "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": { - "p-limit": "^1.1.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "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, + "node_modules/d3-ease": { + "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": ">=4" + "node": ">=12" } }, - "node_modules/eslint-plugin-es": { + "node_modules/d3-fetch": { "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, + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "d3-dsv": "1 - 3" }, "engines": { - "node": ">=8.10.0" + "node": ">=12" } }, - "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, + "node_modules/d3-force": { + "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": { - "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" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "node": ">=12" } }, - "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" - }, + "node_modules/d3-format": { + "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": ">=0.10.0" + "node": ">=12" } }, - "node_modules/eslint-plugin-import/node_modules/resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", - "dev": true, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "dependencies": { - "is-core-module": "^2.8.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "d3-array": "2.5.0 - 3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=12" } }, - "node_modules/eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "^4.0.1" - }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - } + "node": ">=12" } }, - "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, + "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": { - "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" + "d3-color": "1 - 3" }, "engines": { - "node": ">=8.10.0" + "node": ">=12" } }, - "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/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-polygon": { + "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": "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/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, + "node_modules/d3-random": { + "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": ">=6" + "node": ">=12" } }, - "node_modules/eslint-plugin-react": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", - "integrity": "sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==", - "dev": true, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flatmap": "^1.2.5", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.0", - "object.values": "^1.1.5", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.6" + "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": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "node": ">=12" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", - "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", - "dev": true, - "engines": { - "node": ">=10" + "node_modules/d3-scale-chromatic": { + "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" }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "engines": { + "node": ">=12" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, + "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": { - "esutils": "^2.0.2" + "internmap": "1 - 2" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "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, + "node_modules/d3-selection": { + "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": ">=4.0" + "node": ">=12" } }, - "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, + "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==", "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "d3-path": "1" } }, - "node_modules/eslint-plugin-react/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/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "dependencies": { + "d3-array": "2" } }, - "node_modules/eslint-plugin-standard": { + "node_modules/d3-time-format": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz", - "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==", - "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": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "d3-time": "1 - 3" }, "engines": { - "node": ">=8.0.0" + "node": ">=12" } }, - "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, + "node_modules/d3-timer": { + "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": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "dependencies": { - "estraverse": "^5.2.0" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" }, "engines": { - "node": ">=4.0" + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "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, + "node_modules/d3-zoom": { + "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 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, "engines": { - "node": ">=4.0" + "node": ">=12" } }, - "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, + "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": { - "eslint-visitor-keys": "^1.1.0" + "internmap": "1 - 2" }, "engines": { - "node": ">=6" + "node": ">=12" } }, - "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, + "node_modules/d3/node_modules/d3-path": { + "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": ">=4" + "node": ">=12" } }, - "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, + "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": { - "@babel/highlight": "^7.10.4" + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" } }, - "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, + "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": { - "color-convert": "^2.0.1" + "d3-array": "2 - 3" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/eslint/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/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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" }, "engines": { "node": ">=10" } }, - "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==", + "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, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "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": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "color-name": "~1.1.4" - }, + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, "engines": { - "node": ">=7.0.0" + "node": ">=0.10.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==", + "node_modules/decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "node_modules/decode-uri-component": { + "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/deeks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/deeks/-/deeks-2.5.1.tgz", + "integrity": "sha512-fqrBeUz7f1UqaXDRzVB5RG2EfPk15HJRrb2pMZj8mLlSTtz4tRPsK5leFOskoHFPuyZ6+7aRM9j657fvXLkJ7Q==", + "engines": { + "node": ">= 12" + } + }, + "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", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "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==", + "node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "dependencies": { - "ms": "2.1.2" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/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==", + "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, - "engines": { - "node": ">=10" + "dependencies": { + "clone": "^1.0.2" } }, - "node_modules/eslint/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==", + "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": { - "type-fest": "^0.20.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "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" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/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/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "engines": { - "node": ">= 4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/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/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/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, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, "dependencies": { - "has-flag": "^4.0.0" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "node_modules/define-property/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, - "engines": { - "node": ">=10" + "dependencies": { + "kind-of": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, - "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==", + "node_modules/define-property/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": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "kind-of": "^6.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=0.10.0" } }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "node_modules/define-property/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, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" }, "engines": { - "node": ">=0.4.0" + "node": ">=0.10.0" } }, - "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==", + "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" + "robust-predicates": "^3.0.0" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "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": ">=4.0" + "node": ">=0.4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true, "engines": { - "node": ">=4.0" + "node": ">= 0.6" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "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": ">= 0.6" + "node": ">=8" } }, - "node_modules/eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "node_modules/detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, - "node_modules/events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "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, - "engines": { - "node": ">=0.8.x" + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" } }, - "node_modules/eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", + "node_modules/diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", "dev": true, - "dependencies": { - "original": "^1.0.0" - }, "engines": { - "node": ">=0.12.0" + "node": ">= 10.14.2" } }, - "node_modules/exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", - "dev": true - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "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": ">=6" + "node": ">=8" } }, - "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==", + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "@leichtgewicht/ip-codec": "^2.0.1" }, "engines": { - "node": ">=4.8" + "node": ">=6" } }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, + "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": ">=4" + "node": ">=12" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "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": { - "shebang-regex": "^1.0.0" + "esutils": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "node_modules/dom-accessibility-api": { + "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": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "utila": "~0.4" } }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/dom-serializer": { + "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": { - "isexe": "^2.0.0" + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" }, - "bin": { - "which": "bin/which" + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/exit": { + "node_modules/dom-walk": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, + "node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true, - "engines": { - "node": ">= 0.8.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] }, - "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=", + "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": { - "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" + "webidl-conversions": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/expand-brackets/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=", + "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, - "dependencies": { - "is-descriptor": "^0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/expand-brackets/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/domhandler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" + "domelementtype": "^2.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "dev": true, "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" }, - "engines": { - "node": ">= 10.14.2" + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/expect/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/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/expect/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/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": { - "color-name": "~1.1.4" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" } }, - "node_modules/expect/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/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/express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "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==", + "dev": true, "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.1", - "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.7", - "qs": "6.9.6", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" + "safe-buffer": "^5.0.1" } }, - "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/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.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", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "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/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "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, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, - "node_modules/extend-shallow/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, - "dependencies": { - "is-plain-object": "^2.0.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.10.0" + "node": ">= 0.8" } }, - "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==", + "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": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" + "once": "^1.4.0" } }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "node_modules/enhanced-resolve": { + "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": { - "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" + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.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=", + "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": { - "is-descriptor": "^1.0.0" + "ansi-colors": "^4.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6" } }, - "node_modules/extglob/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/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "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==", + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "dev": true, - "dependencies": { - "kind-of": "^6.0.0" + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "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, + "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==", "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" + "is-arrayish": "^0.2.1" } }, - "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==", + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "node_modules/es-abstract": { + "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": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "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-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", + "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.10.0" + "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/extsprintf": { + "node_modules/es-errors": { "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" - ] + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/faker": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", - "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", + "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/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==" - }, - "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==", - "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": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/fast-json-stable-stringify": { + "node_modules/es-set-tostringtag": { "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": "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/fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "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==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "dependencies": { - "websocket-driver": ">=0.5.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=0.8.0" + "node": ">= 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==", + "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": { - "bser": "2.1.1" + "has": "^1.0.3" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "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": { - "escape-string-regexp": "^1.0.5" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 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==", + "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, - "dependencies": { - "flat-cache": "^3.0.4" + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "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/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" - }, + "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/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" - }, + "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" + "node": ">=0.8.0" } }, - "node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "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": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "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": ">=8" + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" } }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "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, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=4.0" } }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "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": { - "p-locate": "^4.1.0" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "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": { - "p-limit": "^2.2.0" + "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": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/find-cache-dir/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/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": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/find-cache-dir/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==", + "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, - "dependencies": { - "find-up": "^4.0.0" - }, + "optional": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/find-root": { - "version": "1.1.0", - "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==", + "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": { - "locate-path": "^3.0.0" + "prelude-ls": "~1.1.2" }, "engines": { - "node": ">=6" + "node": ">= 0.8.0" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "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": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "@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/flat-cache/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/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, - "dependencies": { - "glob": "^7.1.3" - }, "bin": { - "rimraf": "bin.js" + "eslint-config-prettier": "bin/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true + "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/focus-lock": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.9.2.tgz", - "integrity": "sha512-YtHxjX7a0IC0ZACL5wsX8QdncXofWpGPNoVMuI/nZUrPGp6LmNI6+D5j0pPj+v8Kw5EpweA+T5yImK0rnWf7oQ==", + "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": { - "tslib": "^2.0.3" - }, - "engines": { - "node": ">=10" + "ms": "^2.1.1" } }, - "node_modules/focus-lock/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/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/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==", + "node_modules/eslint-module-utils": { + "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": "^3.2.7", + "find-up": "^2.1.0" + }, "engines": { - "node": ">=4.0" + "node": ">=4" } }, - "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==", + "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": { - "is-callable": "^1.1.3" + "ms": "^2.1.1" } }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "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": ">=0.10.0" + "node": ">=4" } }, - "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/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": "*" + "node": ">=4" } }, - "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==", + "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", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "p-try": "^1.0.0" }, "engines": { - "node": ">= 0.12" + "node": ">=4" } }, - "node_modules/formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "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": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" + "p-limit": "^1.1.0" }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" + "engines": { + "node": ">=4" } }, - "node_modules/formidable/node_modules/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", + "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": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/formik": { - "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" - } - ], + "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": { - "deepmerge": "^2.1.1", - "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "react-fast-compare": "^2.0.1", - "tiny-warning": "^1.0.2", - "tslib": "^1.10.0" + "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" }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "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": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, - "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=", + "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": { - "map-cache": "^0.2.2" + "esutils": "^2.0.2" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/framer-motion": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-4.1.17.tgz", - "integrity": "sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==", + "node_modules/eslint-plugin-jest": { + "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": { - "framesync": "5.3.0", - "hey-listen": "^1.0.8", - "popmotion": "9.3.6", - "style-value-types": "4.1.4", - "tslib": "^2.1.0" + "@typescript-eslint/utils": "^5.10.0" }, - "optionalDependencies": { - "@emotion/is-prop-valid": "^0.8.2" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "react": ">=16.8 || ^17.0.0", - "react-dom": ">=16.8 || ^17.0.0" - } - }, - "node_modules/framer-motion/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/framesync": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz", - "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/framesync/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "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-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" + "@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/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==", + "node_modules/eslint-plugin-react": { + "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": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "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": ">=10" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "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==", + "node_modules/eslint-plugin-react-hooks": { + "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, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "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/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "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": { + "node_modules/eslint-plugin-react/node_modules/doctrine": { "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==", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=0.10.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==", + "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": "6.* || 8.* || >= 10.*" + "node": ">=4.0" } }, - "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==", + "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": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "engines": { - "node": ">=6" + "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/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==", + "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/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "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": { - "pump": "^3.0.0" + "eslint-visitor-keys": "^1.1.0" }, "engines": { "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==", + "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, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "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, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "@babel/highlight": "^7.10.4" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "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": { - "assert-plus": "^1.0.0" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "node_modules/eslint/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": { - "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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "*" + "node": ">=10" } }, - "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==", + "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": { - "is-glob": "^4.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 6" + "node": ">=7.0.0" } }, - "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==", + "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/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "node_modules/eslint/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": { - "min-document": "^2.19.0", - "process": "^0.11.10" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "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, - "dependencies": { - "ini": "2.0.0" - }, "engines": { "node": ">=10" }, @@ -11276,795 +11957,773 @@ "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==", + "node_modules/eslint/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": ">=4" + "node": ">=10" } }, - "node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "node_modules/eslint/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": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "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": ">=0.10.0" + "node": ">=8" } }, - "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==", + "node_modules/eslint/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, - "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": ">= 4" } }, - "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "node_modules/eslint/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/graphql": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", - "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", - "engines": { - "node": ">= 10.x" - } - }, - "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/eslint/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": { - "@apollographql/apollo-tools": "^0.5.0", - "apollo-server-env": "^3.1.0", - "apollo-server-types": "^0.9.0" + "lru-cache": "^6.0.0" }, - "engines": { - "node": ">=6.0" + "bin": { + "semver": "bin/semver.js" }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.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==", - "dev": true, "engines": { - "node": "^12.20.0 || ^14.15.0 || >= 15.9.0" - }, - "peerDependencies": { - "graphql": "^15.5.3" + "node": ">=10" } }, - "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==", + "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": { - "iterall": "^1.3.0" + "has-flag": "^4.0.0" }, - "peerDependencies": { - "graphql": "^0.10.5 || ^0.11.3 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "engines": { + "node": ">=8" } }, - "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" - }, + "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" }, - "peerDependencies": { - "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" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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/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==", - "deprecated": "This package has been deprecated and now it only exports makeExecutableSchema.\\nAnd it will no longer receive updates.\\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\\nCheck out https://www.graphql-tools.com to learn what package you should use instead", + "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": { - "apollo-link": "^1.2.14", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, - "peerDependencies": { - "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0" + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "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==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "node_modules/espree/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": { - "uuid": "bin/uuid" + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "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=", + "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/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "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": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=6" + "node": ">=0.10" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { - "function-bind": "^1.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.4.0" + "node": ">=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==", + "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, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=4.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "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" + "node": ">=4.0" } }, - "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==", + "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.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "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" - }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=0.8.x" } }, - "node_modules/has-values": { + "node_modules/exec-sh": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", + "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", + "dev": true + }, + "node_modules/execa": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/has-values/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/execa/node_modules/cross-spawn": { + "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": { - "kind-of": "^3.0.2" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "engines": { - "node": ">=0.10.0" + "node": ">=4.8" } }, - "node_modules/has-values/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/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "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=", + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "shebang-regex": "^1.0.0" }, "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==", + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, "bin": { - "he": "bin/he" + "which": "bin/which" } }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "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": ">=8" - } - }, - "node_modules/hey-listen": { - "version": "1.0.8", - "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", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" + "node": ">= 0.8.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/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "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, "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" + "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/hpack.js/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/hpack.js/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==", + "node_modules/expand-brackets/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, "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" + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/hpack.js/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/expand-brackets/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": { - "safe-buffer": "~5.1.0" + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "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==", + "node_modules/expect": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", + "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", "dev": true, "dependencies": { - "whatwg-encoding": "^1.0.5" + "@jest/types": "^26.6.2", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0" }, "engines": { - "node": ">=10" + "node": ">= 10.14.2" } }, - "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==", - "dev": true - }, - "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/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "node_modules/expect/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": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "node_modules/expect/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": ">= 12" + "node": ">=7.0.0" } }, - "node_modules/html-webpack-plugin": { - "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, + "node_modules/expect/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/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "dependencies": { - "@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" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "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.15.1", + "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": ">=10.13.0" + "node": ">= 0.10.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "webpack": "^5.20.0" + "url": "https://opencollective.com/express" } }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "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": [ - "https://github.com/fb55/htmlparser2?sponsor=1", { "type": "github", - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "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", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true + ] }, - "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, + "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.6" + "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-parser-js": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", - "integrity": "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "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, "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "engines": { - "node": ">=8.0.0" + "node": ">=0.10.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/extend-shallow/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, "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" + "is-plain-object": "^2.0.4" }, "engines": { - "node": ">=4.0.0" + "node": ">=0.10.0" } }, - "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/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "dependencies": { - "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", "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/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=", + "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, "dependencies": { - "is-extendable": "^0.1.0" + "is-descriptor": "^1.0.0" }, "engines": { "node": ">=0.10.0" } }, - "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=", + "node_modules/extglob/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": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "is-extendable": "^0.1.0" }, "engines": { "node": ">=0.10.0" } }, - "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/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, "dependencies": { - "is-extendable": "^0.1.0" + "kind-of": "^6.0.0" }, "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=", + "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, "dependencies": { - "kind-of": "^3.0.2" + "kind-of": "^6.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=", + "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, "dependencies": { - "is-buffer": "^1.1.5" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" }, "engines": { "node": ">=0.10.0" } }, - "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/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "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": { - "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" + "@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": ">=0.10.0" + "node": ">=8.6.0" } }, - "node_modules/http-proxy-middleware/node_modules/to-regex-range": { + "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": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fast-safe-stringify": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "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.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "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", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "node_modules/fastq": { + "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": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" + "reusify": "^1.0.4" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "websocket-driver": ">=0.5.1" }, "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" + "node": ">=0.8.0" } }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "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, - "engines": { - "node": ">=8.12.0" + "dependencies": { + "bser": "2.1.1" } }, - "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==", + "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": { - "safer-buffer": ">= 2.1.2 < 3" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=0.10.0" + "node": "^10.12.0 || >=12.0.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 - }, - "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "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": ">= 4" + "node": ">=8" } }, - "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.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "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, + "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": ">=4" + "node": ">= 0.8" } }, - "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==", + "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", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" }, "engines": { "node": ">=8" } }, - "node_modules/import-local/node_modules/find-up": { + "node_modules/find-cache-dir/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", @@ -12077,7 +12736,7 @@ "node": ">=8" } }, - "node_modules/import-local/node_modules/locate-path": { + "node_modules/find-cache-dir/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", @@ -12089,7 +12748,7 @@ "node": ">=8" } }, - "node_modules/import-local/node_modules/p-locate": { + "node_modules/find-cache-dir/node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", @@ -12101,7 +12760,7 @@ "node": ">=8" } }, - "node_modules/import-local/node_modules/path-exists": { + "node_modules/find-cache-dir/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==", @@ -12110,1659 +12769,1889 @@ "node": ">=8" } }, - "node_modules/import-local/node_modules/pkg-dir": { + "node_modules/find-cache-dir/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" + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "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/flat-cache/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/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "node_modules/focus-lock": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", + "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", + "dependencies": { + "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.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.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "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, "engines": { - "node": ">=0.8.19" + "node": ">=0.10.0" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "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": ">=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "node_modules/formidable": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", "dev": true, "dependencies": { - "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" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" }, - "engines": { - "node": ">=8.0.0" + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "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, + "node_modules/formik": { + "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": { - "color-convert": "^2.0.1" + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^1.10.0" }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "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==", + "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, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "map-cache": "^0.2.2" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "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, + "node_modules/framer-motion": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-4.1.17.tgz", + "integrity": "sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==", "dependencies": { - "color-name": "~1.1.4" + "framesync": "5.3.0", + "hey-listen": "^1.0.8", + "popmotion": "9.3.6", + "style-value-types": "4.1.4", + "tslib": "^2.1.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" }, + "peerDependencies": { + "react": ">=16.8 || ^17.0.0", + "react-dom": ">=16.8 || ^17.0.0" + } + }, + "node_modules/framer-motion/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "node_modules/framesync": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz", + "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/framesync/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { - "node": ">=7.0.0" + "node": ">= 0.6" } }, - "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==", + "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/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==", + "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" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "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/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/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "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": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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/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, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, + "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": ">= 0.4" + "node": ">=6.9.0" } }, - "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "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": ">= 0.10" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "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": { - "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, + "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": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "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==", + "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" + "node": ">=8.0.0" } }, - "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, + "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": { - "kind-of": "^3.0.2" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "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=", + "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": { - "is-buffer": "^1.1.5" + "pump": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "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": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "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": { - "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, + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, "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, + }, "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=", + "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, - "dependencies": { - "binary-extensions": "^1.0.0" - }, "engines": { "node": ">=0.10.0" } }, - "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==", + "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": { - "call-bind": "^1.0.0" + "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": ">= 0.4" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "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 - }, - "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==", + "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, - "engines": { - "node": ">= 0.4" + "dependencies": { + "is-glob": "^4.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 6" } }, - "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==", + "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 + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "dev": true, "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" + "min-document": "^2.19.0", + "process": "^0.11.10" } }, - "node_modules/is-core-module": { - "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/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/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=", + "node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "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=", + "node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true, + "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/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "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/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, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, + "node_modules/graphql": { + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", "engines": { - "node": ">=0.10.0" + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.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==", + "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, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.15.0 || >= 15.9.0" + }, + "peerDependencies": { + "graphql": "^16.2.0" } }, - "node_modules/is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "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, - "optional": true, - "bin": { - "is-docker": "cli.js" + "dependencies": { + "iterall": "^1.3.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "graphql": "^15.7.2 || ^16.0.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, + "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": ">=0.10.0" + "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 || ^16.0.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=", + "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", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true, + "optional": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "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==", + "dependencies": { + "function-bind": "^1.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4.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==", + "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, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": ">=6" + "node": ">=4" } }, - "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==", + "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": { - "is-extglob": "^2.1.1" + "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.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/is-interactive": { + "node_modules/has-values": { "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==", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "node_modules/has-values/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, - "engines": { - "node": ">= 0.4" + "dependencies": { + "kind-of": "^3.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "node_modules/has-values/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, - "engines": { - "node": ">=10" + "dependencies": { + "is-buffer": "^1.1.5" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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, + "dependencies": { + "is-buffer": "^1.1.5" + }, "engines": { - "node": ">=0.12.0" + "node": ">=0.10.0" } }, - "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==", - "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": ">= 0.4" - }, - "funding": { - "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/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, - "engines": { - "node": ">=8" + "bin": { + "he": "bin/he" } }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "node_modules/hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + }, + "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/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/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" } }, - "node_modules/is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "node_modules/hpack.js/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/hpack.js/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": { - "is-path-inside": "^2.1.0" - }, - "engines": { - "node": ">=6" + "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/is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "node_modules/hpack.js/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": { - "path-is-inside": "^1.0.2" - }, - "engines": { - "node": ">=6" + "safe-buffer": "~5.1.0" } }, - "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==", + "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": { - "isobject": "^3.0.1" + "whatwg-encoding": "^1.0.5" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "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=", + "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/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==", + "node_modules/html-minifier-terser": { + "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": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" }, - "engines": { - "node": ">= 0.4" + "bin": { + "html-minifier-terser": "cli.js" }, - "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" + "engines": { + "node": ">=12" } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/html-webpack-plugin": { + "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": { - "has-tostringtag": "^1.0.0" + "@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": ">= 0.4" + "node": ">=10.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" } }, - "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "node_modules/htmlparser2": { + "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": { - "has-symbols": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", "dev": true }, - "node_modules/is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", - "dev": true, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "call-bind": "^1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, - "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", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "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" - }, + "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": ">=8" + "node": ">= 0.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=" - }, - "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/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/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, + "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.10.0" - } - }, - "node_modules/isomorphic-unfetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", - "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", - "dependencies": { - "node-fetch": "^2.6.1", - "unfetch": "^4.2.0" + "node": ">= 0.8" } }, - "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=", + "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/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==", + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "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==", + "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": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">= 6" } }, - "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==", + "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": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "ms": "2.1.2" }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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/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/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==", + "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": { - "has-flag": "^4.0.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": ">=8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } } }, - "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==", + "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": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "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": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "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/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==", + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "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/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, + "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 + }, + "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/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==", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", + "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": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" }, "bin": { - "jest": "bin/jest.js" + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "node_modules/jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", + "node_modules/import-local/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/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "node_modules/import-local/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": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/jest-changed-files/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/import-local/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": { - "pump": "^3.0.0" + "p-limit": "^2.2.0" }, "engines": { "node": ">=8" } }, - "node_modules/jest-changed-files/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==", + "node_modules/import-local/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/jest-changed-files/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/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": { - "path-key": "^3.0.0" + "find-up": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, "engines": { - "node": ">= 10.14.2" + "node": ">=0.8.19" } }, - "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==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "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/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "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" + "once": "^1.3.0", + "wrappy": "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==", + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "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": { - "color-name": "~1.1.4" + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" } }, - "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/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/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==", + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/jest-cli/node_modules/supports-color": { + "node_modules/intro.js": { "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, + "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", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dependencies": { - "has-flag": "^4.0.0" - }, + "loose-envify": "^1.0.0" + } + }, + "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": ">=8" + "node": ">= 0.10" } }, - "node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" + "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, + "dependencies": { + "kind-of": "^3.0.2" }, "engines": { - "node": ">= 10.14.2" + "node": ">=0.10.0" } }, - "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==", + "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, "dependencies": { - "color-convert": "^2.0.1" + "is-buffer": "^1.1.5" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-config/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/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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "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": ">=10" + "node": ">=8" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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 }, - "node_modules/jest-config/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==", + "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.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, - "engines": { - "node": ">=8" + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" } }, - "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, + "node_modules/is-core-module": { + "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-flag": "^4.0.0" + "has": "^1.0.3" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "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, "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "kind-of": "^3.0.2" }, "engines": { - "node": ">= 10.14.2" + "node": ">=0.10.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==", + "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, "dependencies": { - "color-convert": "^2.0.1" + "is-buffer": "^1.1.5" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-diff/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/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "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==", + "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, "dependencies": { - "color-name": "~1.1.4" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=0.10.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/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, + "engines": { + "node": ">=0.10.0" + } }, - "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==", + "node_modules/is-docker": { + "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, + "bin": { + "is-docker": "cli.js" + }, "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==", + "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, + "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, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "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, - "dependencies": { - "detect-newline": "^3.0.0" - }, "engines": { - "node": ">= 10.14.2" + "node": ">=6" } }, - "node_modules/jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "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": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 10.14.2" + "node": ">=0.10.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==", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-each/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/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "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/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/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==", + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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, - "dependencies": { - "@babel/runtime": "^7.5.5", - "@types/jest": "^23.0.2", - "chalk": "^2.4.1", - "css": "^2.2.1" + "engines": { + "node": ">=0.12.0" } }, - "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==", - "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==", + "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": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "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" + "engines": { + "node": ">=6" } }, - "node_modules/jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", "dev": true, "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" + "is-path-inside": "^2.1.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=6" } }, - "node_modules/jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", "dev": true, "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "path-is-inside": "^1.0.2" }, "engines": { - "node": ">= 10.14.2" + "node": ">=6" } }, - "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==", + "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.14.2" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "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": { - "@jest/types": "^26.6.2", - "@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": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" + "isobject": "^3.0.1" }, "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "fsevents": "^2.1.2" + "node": ">=0.10.0" } }, - "node_modules/jest-haste-map/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/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/jest-haste-map/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/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": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-haste-map/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/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": { - "has-flag": "^4.0.0" + "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", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "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": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } - }, - "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==", + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "has-symbols": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/jest-jasmine2/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/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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "call-bind": "^1.0.2" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, + "optional": true, "dependencies": { - "has-flag": "^4.0.0" + "is-docker": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "node_modules/isarray": { + "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", + "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, - "dependencies": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, "engines": { - "node": ">= 10.14.2" + "node": ">=0.10.0" } }, - "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" + "node-fetch": "^2.6.1", + "unfetch": "^4.2.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==", + "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, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { "node": ">=8" } }, - "node_modules/jest-matcher-utils/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/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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "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==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "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": { + "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==", @@ -13771,7 +14660,7 @@ "node": ">=8" } }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { + "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==", @@ -13783,155 +14672,193 @@ "node": ">=8" } }, - "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "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": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "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==", + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">=8" + "node": ">=6.0" } }, - "node_modules/jest-message-util/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/istanbul-lib-source-maps/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/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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "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==", + "node_modules/iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", "dev": true }, - "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==", + "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": ">=8" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "node_modules/jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", + "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@jest/core": "^26.6.3", + "import-local": "^3.0.2", + "jest-cli": "^26.6.3" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=8" + "node": ">= 10.14.2" } }, - "node_modules/jest-mock": { + "node_modules/jest-changed-files": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", + "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", "dev": true, "dependencies": { "@jest/types": "^26.6.2", - "@types/node": "*" + "execa": "^4.0.0", + "throat": "^5.0.0" }, "engines": { "node": ">= 10.14.2" } }, - "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==", + "node_modules/jest-changed-files/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "node_modules/jest-changed-files/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": ">= 10.14.2" + "node": ">=8" } }, - "node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "node_modules/jest-changed-files/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/jest-changed-files/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, - "node_modules/jest-resolve-dependencies": { + "node_modules/jest-cli": { "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", + "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", "dev": true, "dependencies": { + "@jest/core": "^26.6.3", + "@jest/test-result": "^26.6.2", "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.6.3", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "prompts": "^2.0.1", + "yargs": "^15.4.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/jest-resolve/node_modules/ansi-styles": { + "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==", @@ -13941,12 +14868,15 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-resolve/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/jest-cli/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -13954,9 +14884,12 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-resolve/node_modules/color-convert": { + "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==", @@ -13968,26 +14901,13 @@ "node": ">=7.0.0" } }, - "node_modules/jest-resolve/node_modules/color-name": { + "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-resolve/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/jest-resolve/node_modules/has-flag": { + "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==", @@ -13996,78 +14916,7 @@ "node": ">=8" } }, - "node_modules/jest-resolve/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/jest-resolve/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/jest-resolve/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/jest-resolve/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { + "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==", @@ -14079,47 +14928,36 @@ "node": ">=8" } }, - "node_modules/jest-resolve/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/jest-runner": { + "node_modules/jest-config": { "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", + "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", "dev": true, "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.6.3", "@jest/types": "^26.6.2", - "@types/node": "*", + "babel-jest": "^26.6.3", "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", + "jest-environment-jsdom": "^26.6.2", + "jest-environment-node": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.6.3", + "jest-regex-util": "^26.0.0", "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" + "jest-validate": "^26.6.2", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/jest-runner/node_modules/ansi-styles": { + "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==", @@ -14131,7 +14969,7 @@ "node": ">=8" } }, - "node_modules/jest-runner/node_modules/chalk": { + "node_modules/jest-config/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -14144,7 +14982,7 @@ "node": ">=10" } }, - "node_modules/jest-runner/node_modules/color-convert": { + "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==", @@ -14156,36 +14994,31 @@ "node": ">=7.0.0" } }, - "node_modules/jest-runner/node_modules/color-name": { + "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-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==", + "node_modules/jest-config/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": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-runner/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/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, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, "engines": { - "node": ">= 10.13.0" + "node": ">=8" } }, - "node_modules/jest-runner/node_modules/supports-color": { + "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==", @@ -14197,48 +15030,22 @@ "node": ">=8" } }, - "node_modules/jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", + "node_modules/jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", "dev": true, "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "bin": { - "jest-runtime": "bin/jest-runtime.js" + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/jest-runtime/node_modules/ansi-styles": { + "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==", @@ -14250,7 +15057,7 @@ "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/chalk": { + "node_modules/jest-diff/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -14263,7 +15070,7 @@ "node": ">=10" } }, - "node_modules/jest-runtime/node_modules/color-convert": { + "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==", @@ -14275,13 +15082,13 @@ "node": ">=7.0.0" } }, - "node_modules/jest-runtime/node_modules/color-name": { + "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-runtime/node_modules/has-flag": { + "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==", @@ -14290,16 +15097,7 @@ "node": ">=8" } }, - "node_modules/jest-runtime/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/jest-runtime/node_modules/supports-color": { + "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==", @@ -14311,47 +15109,35 @@ "node": ">=8" } }, - "node_modules/jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "node_modules/jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", "dev": true, "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" + "detect-newline": "^3.0.0" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/jest-snapshot": { + "node_modules/jest-each": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", "dev": true, "dependencies": { - "@babel/types": "^7.0.0", "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { + "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==", @@ -14363,7 +15149,7 @@ "node": ">=8" } }, - "node_modules/jest-snapshot/node_modules/chalk": { + "node_modules/jest-each/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -14376,7 +15162,7 @@ "node": ">=10" } }, - "node_modules/jest-snapshot/node_modules/color-convert": { + "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==", @@ -14388,13 +15174,13 @@ "node": ">=7.0.0" } }, - "node_modules/jest-snapshot/node_modules/color-name": { + "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-snapshot/node_modules/has-flag": { + "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==", @@ -14403,22 +15189,119 @@ "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==", + "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": { - "lru-cache": "^6.0.0" + "has-flag": "^4.0.0" }, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-emotion": { + "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-environment-jsdom": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "dev": true, + "dependencies": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" }, "engines": { - "node": ">=10" + "node": ">= 10.14.2" } }, - "node_modules/jest-snapshot/node_modules/supports-color": { + "node_modules/jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "dev": true, + "dependencies": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "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-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "@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": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + }, + "engines": { + "node": ">= 10.14.2" + }, + "optionalDependencies": { + "fsevents": "^2.1.2" + } + }, + "node_modules/jest-haste-map/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-haste-map/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": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-haste-map/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==", @@ -14430,24 +15313,36 @@ "node": ">=8" } }, - "node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "node_modules/jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", "dev": true, "dependencies": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", "@jest/types": "^26.6.2", "@types/node": "*", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "throat": "^5.0.0" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/jest-util/node_modules/ansi-styles": { + "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==", @@ -14459,7 +15354,7 @@ "node": ">=8" } }, - "node_modules/jest-util/node_modules/chalk": { + "node_modules/jest-jasmine2/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -14472,7 +15367,7 @@ "node": ">=10" } }, - "node_modules/jest-util/node_modules/color-convert": { + "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==", @@ -14484,13 +15379,13 @@ "node": ">=7.0.0" } }, - "node_modules/jest-util/node_modules/color-name": { + "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-util/node_modules/has-flag": { + "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==", @@ -14499,7 +15394,7 @@ "node": ">=8" } }, - "node_modules/jest-util/node_modules/supports-color": { + "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==", @@ -14511,24 +15406,35 @@ "node": ">=8" } }, - "node_modules/jest-validate": { + "node_modules/jest-leak-detector": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "dev": true, + "dependencies": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", "dev": true, "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", "chalk": "^4.0.0", + "jest-diff": "^26.6.2", "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/ansi-styles": { + "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==", @@ -14540,7 +15446,7 @@ "node": ">=8" } }, - "node_modules/jest-validate/node_modules/chalk": { + "node_modules/jest-matcher-utils/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -14553,7 +15459,7 @@ "node": ">=10" } }, - "node_modules/jest-validate/node_modules/color-convert": { + "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==", @@ -14565,13 +15471,13 @@ "node": ">=7.0.0" } }, - "node_modules/jest-validate/node_modules/color-name": { + "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-validate/node_modules/has-flag": { + "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==", @@ -14580,7 +15486,7 @@ "node": ">=8" } }, - "node_modules/jest-validate/node_modules/supports-color": { + "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==", @@ -14592,25 +15498,27 @@ "node": ">=8" } }, - "node_modules/jest-watcher": { + "node_modules/jest-message-util": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", "dev": true, "dependencies": { - "@jest/test-result": "^26.6.2", + "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" }, "engines": { "node": ">= 10.14.2" } }, - "node_modules/jest-watcher/node_modules/ansi-styles": { + "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==", @@ -14622,7 +15530,7 @@ "node": ">=8" } }, - "node_modules/jest-watcher/node_modules/chalk": { + "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -14635,7 +15543,7 @@ "node": ">=10" } }, - "node_modules/jest-watcher/node_modules/color-convert": { + "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==", @@ -14647,13 +15555,13 @@ "node": ">=7.0.0" } }, - "node_modules/jest-watcher/node_modules/color-name": { + "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-watcher/node_modules/has-flag": { + "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==", @@ -14662,7 +15570,7 @@ "node": ">=8" } }, - "node_modules/jest-watcher/node_modules/supports-color": { + "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==", @@ -14674,485 +15582,259 @@ "node": ">=8" } }, - "node_modules/jest-worker": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.2.tgz", - "integrity": "sha512-0QMy/zPovLfUPyHuOuuU4E+kGACXXE84nRnq6lBVI9GJg5DCBiA97SATi+ZP8CpiJwEQy1oCPjRBf8AnLjN+Ag==", + "node_modules/jest-mock": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", + "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", "dev": true, "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "@jest/types": "^26.6.2", + "@types/node": "*" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 10.14.2" } }, - "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==", + "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": ">=8" + "node": ">=6" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", "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.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "node": ">= 10.14.2" } }, - "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==", + "node_modules/jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", "dev": true, "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.1.0", - "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", - "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", - "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", - "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.4", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "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==" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "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/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" - }, - "bin": { - "json5": "lib/cli.js" + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" }, "engines": { - "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": ">= 10.14.2" } }, - "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "node_modules/jest-resolve-dependencies": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", + "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", "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", - "ms": "^2.1.1", - "semver": "^5.6.0" + "@jest/types": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.6.2" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">= 10.14.2" } }, - "node_modules/jsonwebtoken/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/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "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": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.6.0" + "node": ">=8" } }, - "node_modules/jsx-ast-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz", - "integrity": "sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==", + "node_modules/jest-resolve/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": { - "array-includes": "^3.1.1", - "object.assign": "^4.1.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4.0" + "node": ">=10" } }, - "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==", + "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": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dev": true, - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } + "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/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "node_modules/jest-resolve/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": { - "json-buffer": "3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "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", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "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": ">=0.10.0" + "node": ">=8" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "node_modules/jest-resolve/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": ">=6" + "node": ">=8" } }, - "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/jest-resolve/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": { - "package-json": "^6.3.0" + "p-limit": "^2.2.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==", + "node_modules/jest-resolve/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": ">=6" + "node": ">=8" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/jest-resolve/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "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=" - }, - "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==", + "node_modules/jest-resolve/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, "engines": { - "node": ">=6.11.5" + "node": ">=8" } }, - "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/jest-resolve/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, "engines": { - "node": ">=8.9.0" + "node": ">=8" } }, - "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==", + "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": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6" + "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-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "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", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "node_modules/lodash.startswith": { - "version": "4.2.1", - "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/jest-resolve/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/log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "node_modules/jest-runner": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", + "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", "dev": true, "dependencies": { - "chalk": "^4.0.0" + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.6.2", + "jest-leak-detector": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">= 10.14.2" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { + "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==", @@ -15164,7 +15846,7 @@ "node": ">=8" } }, - "node_modules/log-symbols/node_modules/chalk": { + "node_modules/jest-runner/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", @@ -15177,7 +15859,7 @@ "node": ">=10" } }, - "node_modules/log-symbols/node_modules/color-convert": { + "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==", @@ -15189,13 +15871,13 @@ "node": ">=7.0.0" } }, - "node_modules/log-symbols/node_modules/color-name": { + "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/log-symbols/node_modules/has-flag": { + "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==", @@ -15204,7 +15886,21 @@ "node": ">=8" } }, - "node_modules/log-symbols/node_modules/supports-color": { + "node_modules/jest-runner/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": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "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==", @@ -15216,6272 +15912,6896 @@ "node": ">=8" } }, - "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==", + "node_modules/jest-runtime": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", + "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true - }, - "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" + "@jest/console": "^26.6.2", + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/globals": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^0.6.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.6.3", + "jest-haste-map": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.4.1" }, "bin": { - "loose-envify": "cli.js" + "jest-runtime": "bin/jest-runtime.js" + }, + "engines": { + "node": ">= 10.14.2" } }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "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": { - "tslib": "^2.0.3" - } - }, - "node_modules/lower-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/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, + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/jest-runtime/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": { - "yallist": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" } }, - "node_modules/lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", - "dev": true, - "bin": { - "lz-string": "bin/bin.js" - } - }, - "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==", + "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": { - "semver": "^6.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=7.0.0" } }, - "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==", + "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/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=", + "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, - "dependencies": { - "tmpl": "1.0.x" + "engines": { + "node": ">=8" } }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "node_modules/jest-runtime/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": ">=0.10.0" + "node": ">=8" } }, - "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=", + "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": { - "object-visit": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "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==" - }, - "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/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": ">=8" } }, - "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/jest-serializer": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", + "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.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=" - }, - "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=", + "@types/node": "*", + "graceful-fs": "^4.2.4" + }, "engines": { - "node": ">= 0.6" + "node": ">= 10.14.2" } }, - "node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "node_modules/jest-snapshot": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", + "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "@babel/types": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-resolve": "^26.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^26.6.2", + "semver": "^7.3.2" }, "engines": { - "node": ">=8" + "node": ">= 10.14.2" } }, - "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" + "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": ">=4" + "node": ">=8" } }, - "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==", + "node_modules/jest-snapshot/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": ">= 0.6" + "node": ">=10" } }, - "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==", + "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": { - "mime-db": "1.44.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.6" + "node": ">=7.0.0" } }, - "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==", + "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": ">=6" + "node": ">=8" } }, - "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==", + "node_modules/jest-snapshot/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": ">=4" + "node": ">=10" } }, - "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=", + "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": { - "dom-walk": "^0.1.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "node_modules/jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + }, "engines": { - "node": ">=4" + "node": ">= 10.14.2" } }, - "node_modules/mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "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": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" + "color-convert": "^2.0.1" }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "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==", + "node_modules/jest-util/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": { - "brace-expansion": "^1.1.7" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "*" + "node": ">=10" } }, - "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==", + "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": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.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==", + "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, - "dependencies": { - "is-plain-object": "^2.0.4" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "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, - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/mq-polyfill": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/mq-polyfill/-/mq-polyfill-1.1.8.tgz", - "integrity": "sha1-wUQZCyEhS/jYsJnn405soriI3BQ=", + "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": ">=6.*" + "node": ">= 10.14.2" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "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==", + "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": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" + "color-convert": "^2.0.1" }, - "bin": { - "multicast-dns": "cli.js" + "engines": { + "node": ">=8" } }, - "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", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "node_modules/jest-validate/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": { - "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" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "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==", + "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": ">= 0.6" + "node": ">=7.0.0" } }, - "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==", + "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/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "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/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "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": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/no-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/jest-watcher": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", + "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^26.6.2", + "string-length": "^4.0.1" + }, + "engines": { + "node": ">= 10.14.2" + } }, - "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==", + "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": "4.x || >=6.0.0" + "node": ">=8" } }, - "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==", + "node_modules/jest-watcher/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": ">= 6.0.0" + "node": ">=10" } }, - "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/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": { - "lodash.get": "^4.4.2" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "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/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=", + "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": ">=0.10.0" + "node": ">=8" } }, - "node_modules/node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", + "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, - "optional": true, "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "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==", + "node_modules/jest-worker": { + "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, - "optional": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=10" + "node": ">= 10.13.0" } }, - "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" + "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/nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "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, - "hasInstallScript": true, "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" - }, - "bin": { - "nodemon": "bin/nodemon.js" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/nodemon/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==", + "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.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "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==", + "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": { - "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" + "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": ">= 8.10.0" + "node": ">=10" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/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": { - "ms": "^2.1.1" + "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/nodemon/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" + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/nodemon/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/nodemon/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "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": { - "picomatch": "^2.2.1" + "deeks": "2.5.1", + "doc-path": "3.0.2" }, "engines": { - "node": ">=8.10.0" + "node": ">= 12" } }, - "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" - }, + "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", + "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==", + "devOptional": true, "bin": { - "nopt": "bin/nopt.js" + "json5": "lib/cli.js" }, "engines": { - "node": "*" + "node": ">=6" } }, - "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==", + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "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-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12", + "npm": ">=6" } }, - "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/jsonwebtoken/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/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "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, "dependencies": { - "path-key": "^2.0.0" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "node_modules/jsx-ast-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz", + "integrity": "sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==", "dev": true, + "dependencies": { + "array-includes": "^3.1.1", + "object.assign": "^4.1.1" + }, "engines": { - "node": ">=4" + "node": ">=4.0" } }, - "node_modules/nth-check": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", - "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "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==", "dev": true, "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "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/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "node_modules/jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "dev": true, - "engines": { - "node": "*" + "dependencies": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" } }, - "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=", + "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/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "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, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/object-copy/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=", + "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": { - "is-descriptor": "^0.1.0" - }, + "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==", + "devOptional": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "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=", + "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": { - "is-buffer": "^1.1.5" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/object-inspect": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", - "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", + "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=" + }, + "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, + "engines": { + "node": ">=6.11.5" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "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==", + "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": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">=8.9.0" } }, - "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/lodash": { + "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.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.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.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" }, - "node_modules/object-path": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", - "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", - "dev": true, - "engines": { - "node": ">= 10.12.0" - } + "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==", + "dev": true }, - "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, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/lodash.startswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", + "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" }, - "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==", + "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", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" + "chalk": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "node_modules/log-symbols/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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", - "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=7.0.0" } }, - "node_modules/object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "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, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "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=", + "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": { - "isobject": "^3.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "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==", + "node_modules/loglevel": { + "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, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "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=", + "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": { - "ee-first": "1.1.1" + "js-tokens": "^3.0.0 || ^4.0.0" }, - "engines": { - "node": ">= 0.8" - } - }, - "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, - "engines": { - "node": ">= 0.8" + "bin": { + "loose-envify": "cli.js" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, "dependencies": { - "wrappy": "1" + "tslib": "^2.0.3" } }, - "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==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - } + "node_modules/lower-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/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/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "is-wsl": "^1.1.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "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=", + "node_modules/lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/optimism": { - "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" + "bin": { + "lz-string": "bin/bin.js" } }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "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": { - "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" + "semver": "^6.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/ora": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.2.0.tgz", - "integrity": "sha512-+wG2v8TUU8EgzPHun1k/n45pXquQ9fHnbXVetl9rRgO6kjZszGGbraF3XPTIdgeA+s1lbRjSEftAnyT0w8ZMvQ==", + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "bl": "^4.0.3", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "log-symbols": "^4.0.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" + "bin": { + "semver": "bin/semver.js" } }, - "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==", + "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.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "tmpl": "1.0.x" } }, - "node_modules/ora/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/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "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==", + "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, "dependencies": { - "color-name": "~1.1.4" + "object-visit": "^1.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=0.10.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/math-expression-evaluator": { + "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/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, + "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": ">=8" + "node": ">= 0.4" } }, - "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" - }, + "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": ">=8" + "node": ">= 0.6" } }, - "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==", + "node_modules/memfs": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz", + "integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==", "dev": true, "dependencies": { - "url-parse": "^1.4.3" + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-to-fsa": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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=", + "node_modules/memfs/node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-core": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz", + "integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==", "dev": true, + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" + }, "engines": { - "node": ">=4" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz", + "integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" }, "engines": { - "node": ">=6" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-node": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz", + "integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==", "dev": true, "dependencies": { - "p-limit": "^2.0.0" + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" }, "engines": { - "node": ">=6" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz", + "integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz", + "integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==", "dev": true, "dependencies": { - "retry": "^0.12.0" + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2" }, "engines": { - "node": ">=6" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz", + "integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==", "dev": true, + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.2" + }, "engines": { - "node": ">=6" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-print": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz", + "integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==", "dev": true, "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" + "@jsonjoy.com/fs-node-utils": "4.57.2", + "tree-dump": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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/memfs/node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz", + "integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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 - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, "dependencies": { - "callsites": "^3.0.0" + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" }, "engines": { - "node": ">=6" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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==", + "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" + "@jsonjoy.com/util": "17.67.0" }, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/memfs/node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/memfs/node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, "engines": { - "node": ">= 0.8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "node_modules/memfs/node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", "dev": true, "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/pascal-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/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "node_modules/memfs/node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", "dev": true, + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "node_modules/memfs/node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "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=", + "node_modules/memfs/node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "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==", + "node_modules/memfs/node_modules/thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" } }, - "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==" - }, - "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==", + "node_modules/memfs/node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "node_modules/memfs/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==", "dev": true }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "engines": { - "node": ">=8.6" + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "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": ">=6" + "node": ">= 8" } }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "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.10.0" + "node": ">= 0.6" } }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "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": { - "pinkie": "^2.0.0" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.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" + "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": ">= 6" + "node": ">=4" } }, - "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" - }, + "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": ">=6" + "node": ">= 0.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, + "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": { - "find-up": "^3.0.0" + "mime-db": "1.52.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 - }, - "node_modules/popmotion": { - "version": "9.3.6", - "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.6.tgz", - "integrity": "sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==", - "dependencies": { - "framesync": "5.3.0", - "hey-listen": "^1.0.8", - "style-value-types": "4.1.4", - "tslib": "^2.1.0" + "node": ">= 0.6" } }, - "node_modules/popmotion/node_modules/tslib": { - "version": "2.3.0", - "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==", + "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, - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" - }, "engines": { - "node": ">= 0.12.0" + "node": ">=6" } }, - "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==", + "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==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "dom-walk": "^0.1.0" } }, - "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==", + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=4" } }, - "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==", + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "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", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "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": ">=0.10.0" + "node": "*" } }, - "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==", + "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": ">= 0.8.0" + "node": ">=16 || 14 >=14.17" } }, - "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=", + "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, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "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, - "bin": { - "prettier": "bin-prettier.js" + "dependencies": { + "is-plain-object": "^2.0.4" }, "engines": { - "node": ">=10.13.0" + "node": ">=0.10.0" } }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "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", + "resolved": "https://registry.npmjs.org/mq-polyfill/-/mq-polyfill-1.1.8.tgz", + "integrity": "sha1-wUQZCyEhS/jYsJnn405soriI3BQ=", "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" + "engines": { + "node": ">=6.*" } }, - "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==", + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/multicast-dns": { + "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": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" }, - "engines": { - "node": ">= 10" + "bin": { + "multicast-dns": "cli.js" } }, - "node_modules/pretty-format/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/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", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "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": ">=8" + "node": ">=0.10.0" } }, - "node_modules/pretty-format/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" - }, + "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.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { - "node": ">=7.0.0" + "node": ">= 0.6" } }, - "node_modules/pretty-format/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/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, - "engines": { - "node": ">= 0.6.0" + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" } }, - "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==", + "node_modules/no-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/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/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/prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, + "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": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">= 6" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/prop-types": { - "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.13.1" - } + "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/property-expr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz", - "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==" + "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/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==", + "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": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "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/pseudolocale": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.2.0.tgz", - "integrity": "sha512-k0OQFvIlvpRdzR0dPVrrbWX7eE9EaZ6gpZtTlFSDi1Gf9tMy9wiANCNu7JZ0drcKgUri/39a2mBbH0goiQmrmQ==", + "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, - "dependencies": { - "commander": "*" + "engines": { + "node": ">=0.10.0" } }, - "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/node-notifier": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", + "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", + "dev": true, + "optional": true, + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.2", + "shellwords": "^0.1.1", + "uuid": "^8.3.0", + "which": "^2.0.2" + } }, - "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==", + "node_modules/node-notifier/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, + "optional": true, "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "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==", + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "devOptional": true + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, "engines": { - "node": ">=6" + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, - "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==", + "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": { - "escape-goat": "^2.0.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", - "engines": { - "node": ">=0.6" + "node": ">= 8.10.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "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, - "engines": { - "node": ">=0.4.x" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/ramda": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", - "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "node_modules/nodemon/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/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "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": { - "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.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", - "dependencies": { - "bytes": "3.1.1", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" }, "engines": { - "node": ">= 0.8" + "node": "*" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "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": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/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=", + "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/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "path-key": "^2.0.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==", - "dependencies": { - "@babel/runtime": "^7.12.13" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0 || ^17.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": ">=4" } }, - "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==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" } }, - "node_modules/react-error-boundary": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", - "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "node_modules/nth-check": { + "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": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" + "boolbase": "^1.0.0" }, - "peerDependencies": { - "react": ">=16.13.1" + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/react-fast-compare": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", - "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + "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/react-focus-lock": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.2.tgz", - "integrity": "sha512-WzpdOnEqjf+/A3EH9opMZWauag7gV0BxFl+EY4ElA4qFqYsUsBLnmo2sELbN5OC30S16GAWMy16B9DLPpdJKAQ==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "focus-lock": "^0.9.1", - "prop-types": "^15.6.2", - "react-clientside-effect": "^1.2.5", - "use-callback-ref": "^1.2.5", - "use-sidecar": "^1.0.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" + "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/react-hot-loader": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz", - "integrity": "sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==", + "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, "dependencies": { - "fast-levenshtein": "^2.0.6", - "global": "^4.3.0", - "hoist-non-react-statics": "^3.3.0", - "loader-utils": "^1.1.0", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.1.0", - "source-map": "^0.7.3" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "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==", + "node_modules/object-copy/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, "dependencies": { - "minimist": "^1.2.0" + "is-descriptor": "^0.1.0" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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, "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "is-buffer": "^1.1.5" }, "engines": { - "node": ">=4.0.0" + "node": ">=0.10.0" } }, - "node_modules/react-hot-loader/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, + "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": ">= 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": ">= 0.4" }, - "peerDependencies": { - "react": "^16.3.0 || ^17.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/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==", - "dev": true + "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/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==", - "dependencies": { - "classnames": "^2.2.6", - "lodash.debounce": "^4.0.8", - "lodash.memoize": "^4.1.2", - "lodash.reduce": "^4.6.0", - "lodash.startswith": "^4.2.1", - "prop-types": "^15.7.2" + "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, + "dependencies": { + "isobject": "^3.0.0" }, - "peerDependencies": { - "react": "^16.12.0 || ^17.0.0", - "react-dom": "^16.12.0 || ^17.0.0" + "engines": { + "node": ">=0.10.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==", + "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": { - "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" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=8.5.0" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/object.entries": { + "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": { - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" }, "engines": { - "node": ">=8.5.0" + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "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.19.1" }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react-router": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", - "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "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/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": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, - "peerDependencies": { - "react": ">=15" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react-router-dom": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz", - "integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==", + "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, "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.1", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "isobject": "^3.0.1" }, - "peerDependencies": { - "react": ">=15" + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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": { - "isarray": "0.0.1" + "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/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/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", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "@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" + "ee-first": "1.1.1" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" + "engines": { + "node": ">= 0.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==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" + "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/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/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" + "wrappy": "1" } }, - "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==", + "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": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^1.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=8.5.0" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "node": ">=6" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, "dependencies": { - "lodash.isequal": "^4.5.0", - "prop-types": "^15.6.0", - "swipe-js-iso": "^2.1.5" + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" }, "engines": { - "node": ">=8.0.0", - "npm": ">=5.5.1" - } - }, - "node_modules/react-table": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz", - "integrity": "sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" + "node": ">=18" }, - "peerDependencies": { - "react": "^16.8.3 || ^17.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", - "dev": true, + "node_modules/optimism": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.1.tgz", + "integrity": "sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==", "dependencies": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.19.1" + "@wry/context": "^0.6.0", + "@wry/trie": "^0.3.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==", + "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": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "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/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==", + "node_modules/ora": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.2.0.tgz", + "integrity": "sha512-+wG2v8TUU8EgzPHun1k/n45pXquQ9fHnbXVetl9rRgO6kjZszGGbraF3XPTIdgeA+s1lbRjSEftAnyT0w8ZMvQ==", + "dev": true, "dependencies": { - "debounce": "^1.2.0" + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" }, - "peerDependencies": { - "react": ">=16.13", - "react-dom": ">=16.13" + "engines": { + "node": ">=10" } }, - "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/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": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "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/ora/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": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=0.10" + "node": ">=10" } }, - "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/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": { - "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" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10.0" + "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/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/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": { - "is-extendable": "^0.1.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "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/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, - "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": ">=8" } }, - "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/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "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/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": { - "kind-of": "^3.0.2" + "p-try": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "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/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "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/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "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" + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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, - "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" + "engines": { + "node": ">=6" } }, - "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==", + "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, - "dependencies": { - "safe-buffer": "~5.1.0" - } + "license": "BlueOak-1.0.0" }, - "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=", + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" + "dot-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "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, + "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", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dependencies": { - "resolve": "^1.9.0" + "callsites": "^3.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, + "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==", "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "@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/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/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true }, - "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": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + "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/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==", + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "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==", + "node_modules/pascal-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/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==", + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "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/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/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/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, - "dependencies": { - "@babel/runtime": "^7.8.4" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/regex-not": { + "node_modules/path-is-inside": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "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, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "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==" + }, + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">= 0.4" + "node": "20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "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==", "engines": { "node": ">=8" } }, - "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==", + "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.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "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": ">=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": ">=4" + "node": ">=6" } }, - "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==", + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.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==", + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "dependencies": { - "rc": "^1.2.8" + "pinkie": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "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": { - "jsesc": "~0.5.0" + "node-modules-regexp": "^1.0.0" }, - "bin": { - "regjsparser": "bin/parser" + "engines": { + "node": ">= 6" } }, - "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=", + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", "dev": true, - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "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=", + "node_modules/pkijs/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==", "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==", + "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, + "license": "MIT" + }, + "node_modules/popmotion": { + "version": "9.3.6", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.6.tgz", + "integrity": "sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==", "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" + "framesync": "5.3.0", + "hey-listen": "^1.0.8", + "style-value-types": "4.1.4", + "tslib": "^2.1.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/popmotion/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "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, "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/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.10" + "node": ">= 0.4" } }, - "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/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": ">= 6" + "node": ">= 0.8.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/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "dev": true, - "dependencies": { - "lodash": "^4.17.19" + "bin": { + "prettier": "bin-prettier.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "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/pretty-error": { + "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": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.12.0" + "lodash": "^4.17.20", + "renderkid": "^3.0.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/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": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" }, "engines": { - "node": ">=0.8" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=0.6" + "node": ">= 10" } }, - "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/pretty-format/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": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.8" - } - }, - "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/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": ">=8" } }, - "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==", + "node_modules/pretty-format/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": ">=0.10.0" + "node": ">=7.0.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=", + "node_modules/pretty-format/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/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/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/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/pretty-format/node_modules/react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "dev": true }, - "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/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" + "node": ">= 0.6.0" } }, - "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=", + "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 }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "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, - "dependencies": { - "lowercase-keys": "^1.0.0" + "engines": { + "node": ">=0.4.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==", + "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": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "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/prop-types": { + "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.13.1" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, + "node_modules/property-expr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz", + "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==" + }, + "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": ">= 4" + "node": ">= 0.10" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "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": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=16.0.0" } }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "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": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "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.* || >= 7.*" + "node": ">=6" } }, - "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/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "dev": true, - "engines": { - "node": ">=0.12.0" + "dependencies": { + "tslib": "^2.8.1" } }, - "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/pvtsutils/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==", "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==", + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dependencies": { - "tslib": "^1.9.0" + "side-channel": "^1.1.0" }, "engines": { - "npm": ">=2.0.0" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "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": { - "ret": "~0.1.10" - } + "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/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "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/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, + "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": { - "@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" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">= 0.8" } }, - "node_modules/sane/node_modules/anymatch": { + "node_modules/raw-body/node_modules/depd": { "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" + "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/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, + "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": { - "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" + "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/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/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, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "is-extendable": "^0.1.0" + "loose-envify": "^1.1.0" }, "engines": { "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=", - "dev": true, + "node_modules/react-clientside-effect": { + "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": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "@babel/runtime": "^7.12.13" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "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, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { - "is-extendable": "^0.1.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "react": "^18.2.0" } }, - "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, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", "dependencies": { - "kind-of": "^3.0.2" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=0.10.0" + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" } }, - "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, + "node_modules/react-fast-compare": { + "version": "2.0.4", + "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": { - "is-buffer": "^1.1.5" + "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/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, + "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": { - "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" + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + }, + "node_modules/react-focus-lock": { + "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": "^1.3.5", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.2", + "use-sidecar": "^1.1.2" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "@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/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/react-hot-loader": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz", + "integrity": "sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==", "dev": true, "dependencies": { - "remove-trailing-separator": "^1.0.1" + "fast-levenshtein": "^2.0.6", + "global": "^4.3.0", + "hoist-non-react-statics": "^3.3.0", + "loader-utils": "^1.1.0", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0", + "source-map": "^0.7.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, - "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/react-hot-loader/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": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "minimist": "^1.2.0" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "node_modules/react-hot-loader/node_modules/loader-utils": { + "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": { - "xmlchars": "^2.2.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">=4.0.0" } }, - "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==", + "node_modules/react-hot-loader/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, - "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "engines": { + "node": ">= 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": { + "@types/react": ">=0.0.0 <=99", + "react": ">=0.0.0 <=99" + } + }, + "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/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": ">= 8.9.0" + "node": ">=0.10.0" } }, - "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/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", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", "dev": true }, - "node_modules/selfsigned": { - "version": "1.10.14", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", - "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", - "dev": true, + "node_modules/react-phone-input-2": { + "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": { - "node-forge": "^0.10.0" + "classnames": "^2.2.6", + "lodash.debounce": "^4.0.8", + "lodash.memoize": "^4.1.2", + "lodash.reduce": "^4.6.0", + "lodash.startswith": "^4.2.1", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "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/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/react-remove-scroll": { + "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.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": ">=10" + }, + "peerDependencies": { + "@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/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, + "node_modules/react-remove-scroll-bar": { + "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": { - "semver": "^6.3.0" + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "peerDependencies": { + "@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/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==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "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/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "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-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", "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.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "@remix-run/router": "1.23.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" } }, - "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/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, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", "dependencies": { - "randombytes": "^2.1.0" + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" } }, - "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/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": { - "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" + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" }, - "engines": { - "node": ">= 0.8.0" + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "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, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "peerDependencies": { + "@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/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/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/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "node_modules/react-table": { + "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": "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": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.0" }, - "engines": { - "node": ">= 0.8.0" + "peerDependencies": { + "react": "^18.2.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=", + "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/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, + "node_modules/react-use-measure": { + "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": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "debounce": "^1.2.1" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" } }, - "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=", + "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": { - "is-extendable": "^0.1.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, - "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.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "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": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "picomatch": "^2.2.1" }, - "bin": { - "sha.js": "bin.js" + "engines": { + "node": ">=8.10.0" } }, - "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==", + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, + "license": "MIT", "dependencies": { - "kind-of": "^6.0.2" + "resolve": "^1.20.0" }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" } }, - "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==", + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "dependencies": { - "shebang-regex": "^3.0.0" + "indent-string": "^4.0.0", + "strip-indent": "^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/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==", + "dependencies": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" } }, - "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/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/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, + "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": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "balanced-match": "^1.0.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==", + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "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/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "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": { + "regenerate": "^1.4.2" + }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "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==", + "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": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "@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, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">=0.10.0" } }, - "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==", + "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": { - "color-convert": "^2.0.1" + "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/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/mysticatea" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "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": ">=7.0.0" + "node": ">=4" } }, - "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==", + "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/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "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": { - "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" + "jsesc": "~0.5.0" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "regjsparser": "bin/parser" } }, - "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==", + "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": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "bin": { + "jsesc": "bin/jsesc" } }, - "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=", + "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-descriptor": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.10" } }, - "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==", + "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": { - "kind-of": "^6.0.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/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==", + "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": { - "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==", + "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": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10" } }, - "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==", + "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": { - "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=", + "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": { - "is-buffer": "^1.1.5" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon/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, + "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/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dependencies": { - "is-descriptor": "^0.1.0" + "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/snapdragon/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/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": { - "is-extendable": "^0.1.0" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/snapdragon/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==", + "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": { - "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" + "engines": { + "node": ">=8" } }, - "node_modules/sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" + "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/sockjs-client": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", - "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==", - "dev": true, - "dependencies": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.3" - } + "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/sockjs-client/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "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": { - "ms": "^2.1.1" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" } }, - "node_modules/sockjs-client/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/sockjs-client/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/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==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "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, - "bin": { - "uuid": "bin/uuid" + "engines": { + "node": ">=0.12" } }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, - "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==", + "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/source-map-loader": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-2.0.2.tgz", - "integrity": "sha512-yIYkFOsKn+OdOirRJUPQpnZiMkF74raDVQjj5ni3SzbOiA57SabeX80R5zyMQAKpvKySA3Z4a85vFX3bvpC6KQ==", + "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": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.2", - "source-map-js": "^0.6.2" + "glob": "^7.1.3" }, + "bin": { + "rimraf": "bin.js" + } + }, + "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": ">= 10.13.0" + "node": "6.* || >= 7.*" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "engines": { + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "queue-microtask": "^1.2.2" } }, - "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==", + "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/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": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" + "ret": "~0.1.10" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "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/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": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.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": { + "sane": "src/cli.js" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "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==", + "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_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/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 - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "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": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.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==", + "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": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "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/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 - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "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": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" + "is-extendable": "^0.1.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, - "node_modules/spdy-transport": { + "node_modules/sane/node_modules/is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "dependencies": { - "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" + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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": { - "ms": "^2.1.1" + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/spdy-transport/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/spdy/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/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": { - "ms": "^2.1.1" + "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/spdy/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/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==", + "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": { - "extend-shallow": "^3.0.0" + "remove-trailing-separator": "^1.0.1" }, "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/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "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": { - "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" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" }, "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", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", "dev": true, "dependencies": { - "escape-string-regexp": "^2.0.0" + "xmlchars": "^2.2.0" }, "engines": { "node": ">=10" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "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/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": ">=8" + "node": ">= 8.9.0" } }, - "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=", + "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/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/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/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "dev": true, "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/static-extend/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=", + "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/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { - "is-descriptor": "^0.1.0" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "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.6" + "node": ">= 0.8" } }, - "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, + "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/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": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "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": { + "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" + }, "engines": { - "node": ">=4", - "npm": ">=6" + "node": ">= 0.8.0" } }, - "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "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.8.0" + "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, + "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/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": { - "safe-buffer": "~5.2.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.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==", + "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/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/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "node_modules/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": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" + "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": ">=10" + "node": ">= 0.4" } }, - "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==", + "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": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", - "integrity": "sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==", + "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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", - "side-channel": "^1.0.4" + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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": { + "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/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==", + "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, + "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/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": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "kind-of": "^6.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "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==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "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/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "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": { - "ansi-regex": "^5.0.1" + "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/strip-bom": { + "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "node_modules/shell-quote": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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, - "engines": { - "node": ">=6" - } + "optional": true }, - "node_modules/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, + "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": { - "min-indent": "^1.0.0" + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "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": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/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==", + "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": { - "hey-listen": "^1.0.8", - "tslib": "^2.1.0" + "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/style-value-types/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/stylis": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", - "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" - }, - "node_modules/subscriptions-transport-ws": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", - "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", + "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": { - "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" + "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" }, - "peerDependencies": { - "graphql": "^15.7.2 || ^16.0.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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/superagent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.0.2.tgz", - "integrity": "sha512-2Kx35bZxLLJMBKtuXezxvD0aZQ7l923VwoCn7EtUx+aFxdG7co7PeRIddfrNtvvMuGaLZXA0mKzX+yWRhjrJ7A==", + "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/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": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.3", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "^2.5.0", - "qs": "^6.10.1", - "readable-stream": "^3.6.0", - "semver": "^7.3.5" + "semver": "~7.0.0" }, "engines": { - "node": ">=6.4.0 <13 || >=14" + "node": ">=8.10.0" } }, - "node_modules/superagent/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "dependencies": { - "ms": "2.1.2" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/superagent/node_modules/form-data": { + "node_modules/slice-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "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==", + "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, - "bin": { - "mime": "cli.js" + "dependencies": { + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/superagent/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/superagent/node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "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": { - "side-channel": "^1.0.4" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=7.0.0" } }, - "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==", + "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, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "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": ">=10" + "node": ">=0.10.0" } }, - "node_modules/supertest": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.1.tgz", - "integrity": "sha512-2kBKhfZgnPLmjpzB0n7A2ZnEAWTaLXq4bn3EEVY9w8rUpLyIlSusqKKvWA1Cav7hxXBnXGpxBsSeOHj5wQGe1Q==", + "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": { - "methods": "^1.1.2", - "superagent": "^7.0.2" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.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==", + "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, "dependencies": { - "has-flag": "^3.0.0" + "is-descriptor": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/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==", + "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, "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "kind-of": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "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, + "dependencies": { + "kind-of": "^6.0.0" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "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, "dependencies": { - "has-flag": "^4.0.0" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "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==", + "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, - "engines": { - "node": ">= 0.4" + "dependencies": { + "kind-of": "^3.2.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", "engines": { - "node": ">=8.0.0", - "npm": ">=5.5.1" + "node": ">=0.10.0" } }, - "node_modules/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==", + "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, + "dependencies": { + "is-buffer": "^1.1.5" + }, "engines": { - "node": ">=0.10" + "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.5", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz", - "integrity": "sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw==", + "node_modules/snapdragon/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, "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" + "is-descriptor": "^0.1.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=0.10.0" } }, - "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==", + "node_modules/snapdragon/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": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "is-extendable": "^0.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=0.10.0" } }, - "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==", + "node_modules/snapdragon/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, - "engines": { - "node": ">=6" + "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/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==", + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", + "node_modules/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, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, "engines": { - "node": ">=10" - }, - "peerDependencies": { - "acorn": "^8.5.0" - }, - "peerDependenciesMeta": { - "acorn": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.5.tgz", - "integrity": "sha512-3luOVHku5l0QBeYS8r4CdHYWEGMmIj3H1U64jgkdZzECcSOJAyJ9TjuqcQZvw1Y+4AOBN9SeYJPJmFn2cM4/2g==", + "node_modules/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, "dependencies": { - "jest-worker": "^27.0.6", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.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.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "webpack": "^5.72.1" } }, - "node_modules/terser-webpack-plugin/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==", + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/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.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": 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/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==", + "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, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.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==", + "node_modules/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 }, - "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==", + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, "engines": { - "node": ">= 8" + "node": ">=6.0.0" } }, - "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==", + "node_modules/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, "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "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" + } + }, + "node_modules/spdy-transport/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": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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": "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", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "node_modules/spdy-transport/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/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "node_modules/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==" - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "node_modules/spdy/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": { - "os-tmpdir": "~1.0.2" + "ms": "2.1.2" }, "engines": { - "node": ">=0.6.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "node_modules/spdy/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/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=", - "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=", + "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, "dependencies": { - "kind-of": "^3.0.2" + "extend-shallow": "^3.0.0" }, "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=", + "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/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, "dependencies": { - "is-buffer": "^1.1.5" + "escape-string-regexp": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "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==", + "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": ">=6" + "node": ">=8" } }, - "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==", + "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, "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "engines": { "node": ">=0.10.0" } }, - "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==", + "node_modules/static-extend/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, "dependencies": { - "is-number": "^7.0.0" + "is-descriptor": "^0.1.0" }, "engines": { - "node": ">=8.0" + "node": ">=0.10.0" } }, - "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=" - }, - "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==", + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, "engines": { - "node": ">=0.6" + "node": ">= 0.6" } }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" - }, - "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==", + "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": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" + "safe-buffer": "~5.2.0" } }, - "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==", + "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 + }, + "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": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, "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": ">=10" } }, - "node_modules/tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "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": { - "punycode": "^2.1.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/ts-invariant": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.9.4.tgz", - "integrity": "sha512-63jtX/ZSwnUNi/WhXjnK8kz4cHHpYS60AnmA6ixz17l7E12a5puCWFlNpkne5Rl0J8TBPVHpGjsj4fxs8ObVLQ==", + "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": { - "tslib": "^2.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/ts-invariant/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/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "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": { - "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" + "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" }, - "peerDependencies": { - "typescript": ">=2.7" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { - "minimist": "^1.2.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, - "bin": { - "json5": "lib/cli.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "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==", + "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": { - "tslib": "^1.8.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "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": { - "safe-buffer": "^5.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "*" + "node": ">=8" } }, - "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", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "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, - "dependencies": { - "prelude-ls": "^1.2.1" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=4" } }, - "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==", + "node_modules/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, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "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": ">=8" + "node": ">=6" } }, - "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==", + "node_modules/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, "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "min-indent": "^1.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "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==", + "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/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==", "dependencies": { - "is-typedarray": "^1.0.0" + "hey-listen": "^1.0.8", + "tslib": "^2.1.0" } }, - "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "node_modules/style-value-types/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/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, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "dependencies": { + "component-emitter": "^1.3.0", + "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.6.0", + "qs": "^6.10.3", + "readable-stream": "^3.6.0", + "semver": "^7.3.7" }, "engines": { - "node": ">=4.2.0" + "node": ">=6.4.0 <13 || >=14" } }, - "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==", + "node_modules/superagent/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": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" + "ms": "2.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/unfetch": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", - "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" - }, - "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==", + "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" + "node": ">=4.0.0" } }, - "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==", + "node_modules/superagent/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/superagent/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": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "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==", + "node_modules/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, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.0.0" + }, "engines": { - "node": ">=4" + "node": ">=6.4.0" } }, - "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==", + "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/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==", + "node_modules/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, "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "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==", + "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, - "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==", + "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": ">= 10.0.0" + "node": ">=8" } }, - "node_modules/unpipe": { + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, + "node_modules/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==", "engines": { - "node": ">=0.10.0" + "node": ">=0.10" } }, - "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=", + "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.5", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz", + "integrity": "sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw==", "dev": true, "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.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": ">=0.10.0" + "node": ">=10.0.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=", + "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": { - "isarray": "1.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" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "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=", + "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.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/unset-value/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/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "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": ">=4", - "yarn": "*" + "node": ">=8" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { "node": ">=10" - }, - "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==", + "node_modules/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, "dependencies": { - "color-convert": "^2.0.1" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "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/update-notifier/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "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==", + "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": { - "color-name": "~1.1.4" + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "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/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": ">=7.0.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "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==", + "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 }, - "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==", + "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/update-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==", + "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/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": { - "lru-cache": "^6.0.0" + "callsites": "^3.1.0", + "debug": "^4.2.0", + "is-observable": "^2.1.0", + "observable-fns": "^0.6.1" }, - "bin": { - "semver": "bin/semver.js" + "funding": { + "url": "https://github.com/andywer/threads.js?sponsor=1" }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "tiny-worker": ">= 2" } }, - "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==", + "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": { - "has-flag": "^4.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/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==", + "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, - "dependencies": { - "punycode": "^2.1.0" - } + "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/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "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/tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" }, - "node_modules/url-parse": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", - "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "node_modules/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==" + }, + "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": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "esm": "^3.2.25" } }, - "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=", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "dependencies": { - "prepend-http": "^2.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=4" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "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", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "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": ">=0.10.0" - } - }, - "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==", - "engines": { - "node": ">=8.5.0" + "node": ">=12.0.0" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "@types/react": { + "picomatch": { "optional": true } } }, - "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==", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^1.9.3" - }, + "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": ">=8.5.0" + "node": ">=12" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "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/util.promisify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", - "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", + "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": { - "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" + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true + "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/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "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, + "dependencies": { + "kind-of": "^3.0.2" + }, "engines": { - "node": ">= 0.4.0" + "node": ">=0.10.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==", + "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, - "bin": { - "uuid": "dist/bin/uuid" + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/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 - }, - "node_modules/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==", + "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, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=0.10.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==", + "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" + "node": ">=8.0" } }, - "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==", + "node_modules/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==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, + "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": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" } }, - "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==", + "node_modules/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, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, "engines": { - "node": ">= 0.8" + "node": ">= 4.0.0" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "node_modules/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, - "engines": [ - "node >=0.6.0" - ], "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" } }, - "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, + "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": { - "browser-process-hrtime": "^1.0.0" + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.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, + "node_modules/ts-invariant": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz", + "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==", "dependencies": { - "xml-name-validator": "^3.0.0" + "tslib": "^2.1.0" }, "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/ts-invariant/node_modules/tslib": { + "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.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/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "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": { - "makeerror": "1.0.x" + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "node_modules/tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "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": { - "loose-envify": "^1.0.0" + "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/watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", "dev": true, "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "tslib": "^1.9.3" }, "engines": { - "node": ">=10.13.0" + "node": ">= 6.0.0" } }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "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": { - "minimalistic-assert": "^1.0.0" + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.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=", + "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, - "dependencies": { - "defaults": "^1.0.3" + "engines": { + "node": ">=4" } }, - "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==", + "node_modules/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, "engines": { - "node": ">=10.4" + "node": ">=8" } }, - "node_modules/webpack": { - "version": "5.66.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", - "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", - "dev": 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.3", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "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.3.1", - "webpack-sources": "^3.2.2" - }, - "bin": { - "webpack": "bin/webpack.js" + "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": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/webpack-cli": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", - "integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==", + "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": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.0", - "@webpack-cli/info": "^1.4.0", - "@webpack-cli/serve": "^1.6.0", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=10.13.0" - }, - "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": ">= 0.4" } }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "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": "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": ">= 10" + "node": ">=14.17" } }, - "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==", + "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": { - "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" + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, + "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": ">=10" + "node": ">=4" } }, - "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==", + "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": ">=10.17.0" + "node": ">=4" } }, - "node_modules/webpack-cli/node_modules/is-stream": { + "node_modules/unicode-match-property-value-ecmascript": { "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==", + "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": ">=8" + "node": ">=4" } }, - "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==", + "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, - "dependencies": { - "path-key": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "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==", + "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, "dependencies": { - "mimic-fn": "^2.1.0" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/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==", + "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, "dependencies": { - "webpack-combine-loaders": "2.0.4" + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/webpack-config-utils/node_modules/webpack-combine-loaders": { - "version": "2.0.4", - "integrity": "sha1-J4FNUrgyntZWW+OQCarHY2Hn4iw=", + "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, "dependencies": { - "qs": "^6.5.2" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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, "dependencies": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" + "isarray": "1.0.0" }, "engines": { - "node": ">= 6" + "node": ">=0.10.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/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, - "bin": { - "mime": "cli.js" - }, "engines": { - "node": ">=4.0.0" + "node": ">=0.10.0" } }, - "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==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } + "node_modules/unset-value/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/webpack-dev-server": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz", - "integrity": "sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA==", - "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": { - "ansi-html-community": "0.0.8", - "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.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.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" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 6.11.5" + "update-browserslist-db": "cli.js" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } + "browserslist": ">= 4.21.0" } }, - "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=", + "node_modules/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, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "punycode": "^2.1.0" } }, - "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/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, - "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/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, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, "engines": { "node": ">=0.10.0" } }, - "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/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==", "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" + "tslib": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "peerDependencies": { + "@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/webpack-dev-server/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, + "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.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", "dependencies": { - "is-extendable": "^0.1.0" + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "peerDependencies": { + "@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/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, + "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/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "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": ">=6" + "node": ">= 0.4.0" } }, - "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/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, - "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" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" + "bin": { + "uuid": "dist/bin/uuid" } }, - "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/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 + }, + "node_modules/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, "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.10.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==", + "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": ">=6" + "node": ">= 8" } }, - "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/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": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.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/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, - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">=12" } }, - "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=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "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/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": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "browser-process-hrtime": "^1.0.0" } }, - "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==", + "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, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "xml-name-validator": "^3.0.0" + }, "engines": { - "node": ">= 4.0" + "node": ">=10" } }, - "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/walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", "dev": true, "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "makeerror": "1.0.x" } }, - "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/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "dependencies": { - "is-extglob": "^2.1.0" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.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/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "dependencies": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=6" + "minimalistic-assert": "^1.0.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/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "dev": true, - "engines": { - "node": ">=4" + "dependencies": { + "defaults": "^1.0.3" } }, - "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/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, - "dependencies": { - "kind-of": "^3.0.2" - }, "engines": { - "node": ">=0.10.0" + "node": ">=10.4" } }, - "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=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" + "node_modules/webpack": { + "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.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" + }, + "bin": { + "webpack": "bin/webpack.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "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/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "license": "MIT", "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" + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } } }, - "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==", - "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/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, "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" + "webpack-combine-loaders": "2.0.4" } }, - "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/webpack-config-utils/node_modules/webpack-combine-loaders": { + "version": "2.0.4", + "integrity": "sha1-J4FNUrgyntZWW+OQCarHY2Hn4iw=", "dev": true, - "engines": { - "node": ">=4" + "dependencies": { + "qs": "^6.5.2" } }, - "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/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", "dev": true, "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 4" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } } }, - "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==", + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "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/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/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": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "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/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/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==", "dev": true, "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "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/webpack-dev-middleware/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==", "dev": true, "dependencies": { - "ansi-regex": "^4.1.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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/webpack-dev-middleware/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": { - "ansi-regex": "^2.0.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "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/webpack-dev-server": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz", + "integrity": "sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">=6" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } } }, - "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=", + "node_modules/webpack-dev-server/node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" } }, - "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/webpack-dev-server/node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "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/webpack-dev-server/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "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/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/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": { - "ansi-regex": "^4.1.0" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "dependencies": { - "async-limiter": "~1.0.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "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/webpack-dev-server/node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", "dev": true, - "dependencies": { - "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" + "engines": { + "node": ">= 10" } }, - "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==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "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-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "node_modules/webpack-dev-server/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": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 6" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "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==", + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "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": ">=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-merge": { @@ -21498,23 +22818,58 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.2.tgz", - "integrity": "sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "engines": { "node": ">=10.13.0" } }, + "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": { + "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/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": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "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/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==", + "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.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@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" @@ -21613,16 +22968,25 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "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==", + "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": { - "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" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/wildcard": { @@ -21632,9 +22996,9 @@ "dev": true }, "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==", + "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" @@ -21654,6 +23018,57 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-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-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": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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", @@ -21703,9 +23118,10 @@ } }, "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, "engines": { "node": ">=8.3.0" }, @@ -21722,13 +23138,34 @@ } } }, - "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==", + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", "dev": true, + "dependencies": { + "is-wsl": "^3.1.0" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/xml-name-validator": { @@ -21744,9 +23181,9 @@ "dev": true }, "node_modules/xss": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.10.tgz", - "integrity": "sha512-qmoqrRksmzqSKvgqzN0055UFWY7OKx1/9JWeRswwEVX9fCG5jcYRxa/A2DHcmZX6VJvjzHRQ2STeeVcQkrmLSw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", + "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", "dev": true, "dependencies": { "commander": "^2.20.3", @@ -21778,9 +23215,9 @@ "dev": true }, "node_modules/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==", "engines": { "node": ">= 6" } @@ -21875,15 +23312,6 @@ "node": ">=8" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yup": { "version": "0.32.11", "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", @@ -21905,36 +23333,42 @@ "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" - }, - "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==", - "dev": true, - "dependencies": { - "tslib": "^1.9.3", - "zen-observable": "^0.8.0" - } } }, "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 + }, + "@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" + } + }, "@apollo/client": { - "version": "3.5.7", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.5.7.tgz", - "integrity": "sha512-HSLqTGp3sp/PVIWYLLr5v3fjQSr6Fxg6Z5RQj5Q9ALyseIVudD8GZk1jHplaUblTFMBueXGw3Z6DXObuVAr3tw==", + "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.0.0", + "@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.3", + "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.9.4", + "ts-invariant": "^0.10.3", "tslib": "^2.3.0", - "zen-observable-ts": "^1.2.0" + "zen-observable-ts": "^1.2.5" }, "dependencies": { "tslib": { @@ -21943,9 +23377,9 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "zen-observable-ts": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.3.tgz", - "integrity": "sha512-hc/TGiPkAWpByykMwDcem3SdUgA4We+0Qb36bItSuJC9xD0XVBZoFHYoadAomDSNf64CG8Ydj0Qb8Od8BUWz5g==", + "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" } @@ -21953,9 +23387,9 @@ } }, "@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, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -21969,80 +23403,124 @@ "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/long": "^4.0.0", - "@types/node": "^10.1.0", "long": "^4.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==", + "dev": true, + "requires": { + "@apollo/protobufjs": "1.2.7" + } + }, + "@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, + "requires": {} + }, + "@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": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "lru-cache": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz", + "integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==", "dev": true } } }, - "@apollographql/apollo-tools": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.2.tgz", - "integrity": "sha512-KxZiw0Us3k1d0YkJDhOpVH5rJ+mBfjXcgoRoCcslbgirjgLotKMzOcx4PZ7YTEvvEROmvG7X3Aon41GvMmyGsw==", + "@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 }, - "@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==", + "@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, + "requires": {} + }, + "@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, + "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": { - "xss": "^1.0.8" + "lodash.sortby": "^4.7.0" } }, - "@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==", + "@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, + "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": { - "@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" + "@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" } }, - "@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==", + "@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": {} + }, + "@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": { - "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 - } + "xss": "^1.0.8" } }, "@babel/cli": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.8.tgz", - "integrity": "sha512-FTKBbxyk5TclXOGmwYyqelqP5IF6hMxaeJskd85jbR5jBfYlwqgwAbJwnixi1ZBbTqKfFuAA95mdmUFeSRwyJA==", + "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.0.0", + "glob": "^7.2.0", "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" + "slash": "^2.0.0" }, "dependencies": { "commander": { @@ -22070,44 +23548,55 @@ } }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "requires": { - "@babel/highlight": "^7.16.7" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, "@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==" + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "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, + "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.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "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==", + "devOptional": 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==", + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -22115,97 +23604,128 @@ "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 }, "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 } } }, "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "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==", "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@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" + }, + "dependencies": { + "@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==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + } } }, "@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==", + "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, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@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==", + "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, "requires": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" } }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "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.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.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" }, "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.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 + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "devOptional": 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==", + "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, "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-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" } }, "@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==", + "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, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" } }, "@babel/helper-define-polyfill-provider": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz", - "integrity": "sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==", + "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.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", + "@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", @@ -22213,9 +23733,9 @@ }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "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" @@ -22228,374 +23748,375 @@ "dev": true }, "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 } } }, "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "requires": { - "@babel/types": "^7.16.7" - } + "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==", + "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, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "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/types": "^7.16.7" + "@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==" + }, "@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==", + "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, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.9" } }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "requires": { - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "devOptional": true, "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@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==", + "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, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + "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==", + "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.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" + "@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" } }, "@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==", + "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, "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-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" } }, "@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==", + "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==", + "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.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==", + "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.16.0" + "@babel/types": "^7.18.9" } }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "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.16.7" + "@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==" + }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "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.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + "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.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "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.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" } }, "@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", + "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.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" } }, "@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "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" } }, "@babel/parser": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==" + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "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==", + "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, "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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, "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/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" } }, "@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==", + "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, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", + "@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" } }, "@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==", + "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, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@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" } }, "@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==", + "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, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@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==", + "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, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.9", "@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==", + "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, "requires": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@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==", + "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": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@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==", + "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": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@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.16.7" + "@babel/plugin-transform-parameters": "^7.18.8" } }, "@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==", + "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.16.7", + "@babel/helper-plugin-utils": "^7.18.6", "@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==", + "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": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.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" } }, "@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==", + "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": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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": { - "@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/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/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==", + "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": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-syntax-async-generators": { @@ -22652,6 +24173,15 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, + "@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-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", @@ -22671,11 +24201,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", + "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/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -22751,414 +24282,416 @@ } }, "@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==", + "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-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" } }, "@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==", + "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/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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", + "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/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==", + "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-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "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==", + "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-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@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-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==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "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/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" } }, "@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==", + "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-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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-create-regexp-features-plugin": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" } }, "@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==", + "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-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", - "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.7.tgz", - "integrity": "sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag==", + "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-annotate-as-pure": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-jsx": "^7.16.7", - "@babel/types": "^7.16.7" + "@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-transform-react-jsx-development": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", - "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==", + "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/plugin-transform-react-jsx": "^7.16.7" + "@babel/plugin-transform-react-jsx": "^7.18.6" } }, "@babel/plugin-transform-react-pure-annotations": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", - "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==", + "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-annotate-as-pure": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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": { - "regenerator-transform": "^0.14.2" + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-transform-runtime": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.8.tgz", - "integrity": "sha512-6Kg2XHPFnIarNweZxmzbgYnnWsXxkx9WQUVk2sksBRL80lBC1RAQV3wQagWxdCHiYHqPN+oenwNIuttlYgIbQQ==", + "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-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", + "@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.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 } } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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.16.7" + "@babel/helper-plugin-utils": "^7.18.9" } }, "@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==", + "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-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, "@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", + "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", @@ -23168,51 +24701,51 @@ "@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/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.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", + "@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.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 } } @@ -23231,68 +24764,55 @@ } }, "@babel/preset-react": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", - "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==", + "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-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-react-display-name": "^7.16.7", - "@babel/plugin-transform-react-jsx": "^7.16.7", - "@babel/plugin-transform-react-jsx-development": "^7.16.7", - "@babel/plugin-transform-react-pure-annotations": "^7.16.7" + "@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/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "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.13.4" - } - }, - "@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, - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "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==", "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/traverse": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "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" }, "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.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -23305,12 +24825,12 @@ } }, "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, "@bcoe/v8-coverage": { @@ -23320,625 +24840,1077 @@ "dev": true }, "@chakra-ui/accordion": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-1.4.3.tgz", - "integrity": "sha512-UKpi7xd+hcT/OIDRBRT4vkvVmpym5K9tf3tPyPdwpJcCfCd0D8dvmgZWBM4vCCRPCNfuoyxba5z97witzLCf+g==", + "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": { - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.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" } }, "@chakra-ui/alert": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-1.3.2.tgz", - "integrity": "sha512-+OMeVeGtydpj6nry0zH7qFDt36zEaxckRnufx1BGiCfWdUg6ahVwKXl8qX93Q8w82od7eAoBKMgGJz7IVL5NPw==", + "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": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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" } }, "@chakra-ui/anatomy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-1.2.1.tgz", - "integrity": "sha512-kNS+FiEDTSnwpQUW4dEjZ5745xhkvB0XtmqjY1wpclUSpFfptLZM9QIHPTnBt2bzM9R+idmRRP+WkTt6kyTrLw==", - "requires": { - "@chakra-ui/theme-tools": "^1.3.1" - } + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", + "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==" }, "@chakra-ui/avatar": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-1.3.3.tgz", - "integrity": "sha512-kc/5L0q1oBSuf1fjLp2DLiV18MSWYdG71311Rx3oZyW2tQcO1I2E+RkkGocAmRD6qkBFvIiBEEBUTfHW6X/uVA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.3.0.tgz", + "integrity": "sha512-8gKSyLfygnaotbJbDMHDiJoF38OHXUYVme4gGxZ1fLnQEdPVEaIWfH+NndIjOM0z8S+YEFnT9KyGMUtvPrBk3g==", "requires": { - "@chakra-ui/image": "1.1.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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" } }, "@chakra-ui/breadcrumb": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-1.3.1.tgz", - "integrity": "sha512-b1IoBmtr5FcP2fn5NRbdOdQo2c866OQ/WhcTcZ6UKae1jjik+36/qWE+X+RKzxC6FLfqo5qayV5zSgsnZym7Pg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.2.0.tgz", + "integrity": "sha512-4cWCG24flYBxjruRi4RJREWTGF74L/KzI2CognAW/d/zWR0CjiScuJhf37Am3LFbCySP6WSoyBOtTIoTA4yLEA==", "requires": { - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@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": { + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/button": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-1.5.2.tgz", - "integrity": "sha512-1LyZ7o4g+ZF0Qfwr77Fa3qHtCGhghoIPG2lcUWhhPk+8ygHNF0ZOngfsiDD7aoEj0uXsvlgZ/hdiqv0PmMTRzg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.1.0.tgz", + "integrity": "sha512-95CplwlRKmmUXkdEp/21VkEWgnwcx2TOBG6NfYlsuLBDHSLlo5FKIiE2oSi4zXc4TLcopGcWPNcm/NDaSC5pvA==", "requires": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/spinner": "1.2.1", - "@chakra-ui/utils": "1.9.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" } }, - "@chakra-ui/checkbox": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-1.6.2.tgz", - "integrity": "sha512-eu4MeKWqwFc3zakjMnUi1pGXQ17HzeWbvQHwEZCYgIDk+S9DXopQr8o38zaHzs/MHmlelCzM96IBgTWZXWR/eg==", + "@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": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@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" } }, "@chakra-ui/clickable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-1.2.1.tgz", - "integrity": "sha512-B0CIbKzDMwzG1APeTpW9H2Jl8dkarI1Qstb3hDOy23O+N5TU6lpDdVnXQ7fpFJS6mu5JjFqtkwzGAVZnkkv1rw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", + "integrity": "sha512-flRA/ClPUGPYabu+/GLREZVZr9j2uyyazCAUHAdrTUEdDYCr31SVGhgh7dgKdtq23bOvAQJpIJjw/0Bs0WvbXw==", "requires": { - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/close-button": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-1.2.2.tgz", - "integrity": "sha512-SqeLib0qgMjK3OsO1g5OnAHUmdCC8GMjToNEea7TeSrA44bH9EXVhFTkMMu2PnDVHbQmi4Ee1cuulNJt0UhQ3g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.1.1.tgz", + "integrity": "sha512-gnpENKOanKexswSVpVz7ojZEALl2x5qjLYNqSQGbxz+aP9sOXPfUS56ebyBrre7T7exuWGiFeRwnM0oVeGPaiw==", "requires": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/icon": "3.2.0" } }, "@chakra-ui/color-mode": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-1.3.3.tgz", - "integrity": "sha512-vEMG9PRtVllAxbJE76bM4fRdo+hRZsPX/9cO+gR1YegYQNeobbV9Ive/MKNYQzdlbQ855Ytn6cPGr2hzy9MdRg==", + "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": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" } }, "@chakra-ui/control-box": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-1.1.1.tgz", - "integrity": "sha512-ZFbh85pzzZoiSjGnvLUzMB5BoA8Xm6TBMWvMtzLY5xiFGb9/mBeRDH2KFjr1GJzoqleWKkQwvFD6JM0kXcekpg==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } + "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": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-1.2.2.tgz", - "integrity": "sha512-EljrsJYHpR5tZ1UczzlZ7gwKZs3ijF2tKCAAmsTpRerPbwGFvrmhfFfLexgbc0vxmeuZYUVeuiClLXQT4lvd5w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", + "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", "requires": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/css-reset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz", - "integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==", + "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": "2.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-2.1.1.tgz", - "integrity": "sha512-JasdVaN4MjL7QFo1vMnADy6EtFAlPKT1kTJ1LwMtl9AaF9VFLBsfGxm0L+WQK+3NJMuCSDBXWJB8mV4AQ11Edg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.1.0.tgz", + "integrity": "sha512-VxCIAir08g5w27klLyi7PVo8BxhW4tgU/lxQyujkmi4zx7hT9ZdrcQLAted/dAa+aSIZ14S1oV0Q9lGjsAdxUQ==", "requires": { - "@chakra-ui/react-utils": "^1.2.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0" } }, + "@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": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-1.3.2.tgz", - "integrity": "sha512-WY0dq+hQOIyMAamFj2fECNb1AtOwxoddAbZ6k4/epiKNkaRA5ENzgPdV6Gw3t02fDXv95D0J4V4XcpNgfCz6TA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.1.0.tgz", + "integrity": "sha512-j2JLrUL9wgg4YA6jLlbU88370eCRyor7DZQD9lzpY95tSOXpTljeg3uF9eOmDnCs6fxp3zDWIfkgMm/ExhcGTg==", "requires": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" + "@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" } }, + "@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": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-1.2.1.tgz", - "integrity": "sha512-HYu39nvfaXUrBx+dIDJkFgebNCGEi9oZTfLUKzIJC+zPkmReTDSXV0dzSb/8vCAOq5fph1gFKsdbGy2U98P8GQ==", + "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": { - "@chakra-ui/utils": "1.9.1", - "react-focus-lock": "2.5.2" + "@chakra-ui/dom-utils": "2.1.0", + "react-focus-lock": "^2.9.4" } }, "@chakra-ui/form-control": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-1.5.3.tgz", - "integrity": "sha512-+UkOJJYUSGVATeNK/2b+KJx3qRGAtqpD1sR2mqd9DhgmJ/JUGV8q4LVcXIcVs/7BYNqP9B0JQUBIDlHNuTwlJA==", + "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": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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" } }, "@chakra-ui/hooks": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-1.7.2.tgz", - "integrity": "sha512-XJnya9ugAPYUedtQULKaLYBezE9cZMOkDm0MQl7FEuJKZ9ocHD6Pwpwf9Z03R91XQmcNL8gZ1NS0GT9v/xNl3Q==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.1.tgz", + "integrity": "sha512-RQbTnzl6b1tBjbDPf9zGRo9rf/pQMholsOudTxjy4i9GfTfz6kgp5ValGjQm2z7ng6Z31N1cnjZ1AlSzQ//ZfQ==", "requires": { - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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" + }, + "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/icon": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-2.0.0.tgz", - "integrity": "sha512-/GuU+xIcOIy9uSUUUCu249ZJB/nLDbjWGkfpoSdBwqT4+ytJrKt+0Ckh3Ub14sz3BJD+Z6IiIt6ySOA9+7lbsA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", + "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", "requires": { - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/icons": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-1.1.2.tgz", - "integrity": "sha512-ixrhJhgF6nQOskY4zIsFG8qSiuiUjaKCR+Xmy8MCuGW+/kbztqkzPeDOc0Ypq3JUtH44mU1dP9LjQzxRIviSCA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", "requires": { - "@chakra-ui/icon": "2.0.0", - "@types/react": "^17.0.15" + "@chakra-ui/icon": "3.2.0" } }, "@chakra-ui/image": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-1.1.2.tgz", - "integrity": "sha512-bW4PMONYpWRLJ10di7W5BhYUOcCohNLhiTygOmVRMHWfUk9vmxhORlNu0EaSXUKGXiQ35cTVu4Ysv3L/1xYPRQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", + "integrity": "sha512-bskumBYKLiLMySIWDGcz0+D9Th0jPvmX6xnRMs4o92tT3Od/bW26lahmV2a2Op2ItXeCmRMY+XxJH5Gy1i46VA==", "requires": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/input": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-1.3.3.tgz", - "integrity": "sha512-Fsivp608cKdc2tSQvPXczHtyfU6YHLju+EuAs5pjqKC8ZTwPawBRkCvlTGPPd7VwyTHkp08X5qARCxEgjRFbzw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.1.2.tgz", + "integrity": "sha512-GiBbb3EqAA8Ph43yGa6Mc+kUPjh4Spmxp1Pkelr8qtudpc3p2PJOOebLpd90mcqw8UePPa+l6YhhPtp6o0irhw==", "requires": { - "@chakra-ui/form-control": "1.5.3", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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" } }, "@chakra-ui/layout": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-1.7.1.tgz", - "integrity": "sha512-88kCHKFOD1JVN9/ErUcO5JFSnZ8SURmxJefaJHV7tNWzphjeFLoYdgXYdT9a+rwk80LNDQMvhN68AkXmPTve7Q==", + "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": { - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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" } }, + "@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==" + }, "@chakra-ui/live-region": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-1.1.1.tgz", - "integrity": "sha512-BSdI5gLIffNRETEp6W18kBNg9tL0ZLLzfWGRnuO9tEbox7NrcgqIeLF8mNKwhDOZz88NKHtUOPVzjAUKW1SryQ==", - "requires": { - "@chakra-ui/utils": "1.9.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==", + "requires": {} }, "@chakra-ui/media-query": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-1.2.3.tgz", - "integrity": "sha512-DbStv1VUSBwFj/MNemxRUtoibSoR9ZRcW31UUjtkiIH58cSB5lyEPv9rdD/1HRXpJfSaWQ439Tbuf03gOC4SVQ==", + "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": { - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/menu": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-1.8.3.tgz", - "integrity": "sha512-TehcqHLKlMdkeXTtCYXzWNa6nbAk1cpjGz6OPl2ua0eisoOKgPolpkreCfYrWV9NNk0BmzZn+Su+z60jbhqggA==", - "requires": { - "@chakra-ui/clickable": "1.2.1", - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1" + "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" } }, "@chakra-ui/modal": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-1.10.4.tgz", - "integrity": "sha512-Bw7gF/k9/S4d5CMLjrInAg/cPPfSRdQQgI8qQ4prSSMK37sJwA+kalwuKZaKBZeq3J7CS9YGbtf58uEzyCxLug==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.3.1.tgz", + "integrity": "sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==", "requires": { - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/focus-lock": "1.2.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.4.1" + "@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" } }, "@chakra-ui/number-input": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-1.3.3.tgz", - "integrity": "sha512-nptvORoG+PvvuURkzh0juSYxj3L9HnWIeNncC/7oXgm2AFGxIcqUk8pUJaUNM2mlbLjJZS19/MWlfzgsYJmJrQ==", - "requires": { - "@chakra-ui/counter": "1.2.2", - "@chakra-ui/form-control": "1.5.3", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" - } + "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==" + }, + "@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": "1.7.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-1.7.2.tgz", - "integrity": "sha512-2dGNdU+Xlu0u1OxuelJBKQu7dDKaD5kN9moZMoKpjpXB8Kibh7GM3OO/Z25aMM/E9Kly21qXgHju0Tj/ovBJuw==", + "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": { - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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" } }, "@chakra-ui/popover": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-1.11.1.tgz", - "integrity": "sha512-WV1R2L1V6mbpe6/aInr4yFe6rAu7+pT8Od6Hki5KsIv+4QvoEMYsvJ0VOhaURLLD+NaKXFCsfpc9immluia+AA==", - "requires": { - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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==", + "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" } }, "@chakra-ui/popper": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-2.4.1.tgz", - "integrity": "sha512-cuwnwXx6RUXZGGynVOGG8fEIiMNBXUCy3UqWQD1eEd8200eWQobgNk4Z0YwzKuSzJwp0Auy+j5iKefi5FSkyog==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.1.0.tgz", + "integrity": "sha512-ciDdpdYbeFG7og6/6J8lkTFxsSvwTdMLFkpVylAF6VNC22jssiWfquj2eyD4rJnzkRFPvIWJq8hvbfhsm+AjSg==", "requires": { - "@chakra-ui/react-utils": "1.2.1", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", "@popperjs/core": "^2.9.3" } }, "@chakra-ui/portal": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-1.3.2.tgz", - "integrity": "sha512-dWUCwEZNpPnbR21+eBbEGuvvOcz5AuS+TZ4V0sUdUEI8B3dPUEylJsveZpYjOCS/YAh4xoxbo6I6haRgrfguvw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.1.0.tgz", + "integrity": "sha512-9q9KWf6SArEcIq1gGofNcFPSWEyl+MfJjEUg/un1SMlQjaROOh3zYr+6JAwvcORiX7tyHosnmWC3d3wI2aPSQg==", "requires": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" } }, "@chakra-ui/progress": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-1.2.1.tgz", - "integrity": "sha512-213nN8nbODvD/A23vAtg+r3bRKKatWQHafgmLzeznUcxa/+ac0eVurIS8XSYLRkY4EXQ505re3ZkLhDd98a7QA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", + "requires": { + "@chakra-ui/react-context": "2.1.0" + } + }, + "@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": { + "@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==" + } + } + }, + "@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": { + "@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" + } + }, + "@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" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@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": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + } + }, + "@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/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/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0" + } + }, + "@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/react-use-callback-ref": "2.1.0" + } + }, + "@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/react-use-callback-ref": "2.1.0" + } + }, + "@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-use-callback-ref": "2.1.0" + } + }, + "@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/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/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/react-use-event-listener": "2.1.0" + } + }, + "@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-use-callback-ref": "2.1.0" + } + }, + "@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/react-use-callback-ref": "2.1.0" + } + }, + "@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/theme-tools": "1.3.1", - "@chakra-ui/utils": "1.9.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/provider": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-1.7.5.tgz", - "integrity": "sha512-kyQxbH/nOB2e3XUj+I0kGhZYSJDs4Jje4K8gTZvOGY4uFMr1iSgzqzZ1OzQis81Xf8vAStAkhQi9SihIK8Unsg==", + "@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/css-reset": "1.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/system": "1.9.1", - "@chakra-ui/utils": "1.9.1" + "@zag-js/element-size": "0.10.5" } }, - "@chakra-ui/radio": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-1.4.4.tgz", - "integrity": "sha512-Tz8bl+yhD2pKHUFyNZtP056eawqOUkSA7n2qEebnoH7Zp65B9zwYHkpvL+2nhL5swBwpnGrUpuK3WOqGz4dvXA==", + "@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/form-control": "1.5.3", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" - } - }, - "@chakra-ui/react": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-1.7.5.tgz", - "integrity": "sha512-SVLBKmTBe/4QiiWe2FEVjVlsp+0fnFWFo3hBC48tf4EjQZ7ed+gFtkTd5WYkZkDA3zuLiRvHrqJremP1FiFYxg==", - "requires": { - "@chakra-ui/accordion": "1.4.3", - "@chakra-ui/alert": "1.3.2", - "@chakra-ui/avatar": "1.3.3", - "@chakra-ui/breadcrumb": "1.3.1", - "@chakra-ui/button": "1.5.2", - "@chakra-ui/checkbox": "1.6.2", - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/control-box": "1.1.1", - "@chakra-ui/counter": "1.2.2", - "@chakra-ui/css-reset": "1.1.1", - "@chakra-ui/editable": "1.3.2", - "@chakra-ui/form-control": "1.5.3", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/icon": "2.0.0", - "@chakra-ui/image": "1.1.2", - "@chakra-ui/input": "1.3.3", - "@chakra-ui/layout": "1.7.1", - "@chakra-ui/live-region": "1.1.1", - "@chakra-ui/media-query": "1.2.3", - "@chakra-ui/menu": "1.8.3", - "@chakra-ui/modal": "1.10.4", - "@chakra-ui/number-input": "1.3.3", - "@chakra-ui/pin-input": "1.7.2", - "@chakra-ui/popover": "1.11.1", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/progress": "1.2.1", - "@chakra-ui/provider": "1.7.5", - "@chakra-ui/radio": "1.4.4", - "@chakra-ui/react-env": "1.1.1", - "@chakra-ui/select": "1.2.3", - "@chakra-ui/skeleton": "1.2.5", - "@chakra-ui/slider": "1.5.3", - "@chakra-ui/spinner": "1.2.1", - "@chakra-ui/stat": "1.2.2", - "@chakra-ui/switch": "1.3.2", - "@chakra-ui/system": "1.9.1", - "@chakra-ui/table": "1.3.1", - "@chakra-ui/tabs": "1.6.2", - "@chakra-ui/tag": "1.2.2", - "@chakra-ui/textarea": "1.2.3", - "@chakra-ui/theme": "1.12.3", - "@chakra-ui/toast": "1.5.1", - "@chakra-ui/tooltip": "1.4.3", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "@chakra-ui/react-use-callback-ref": "2.1.0" } }, - "@chakra-ui/react-env": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-1.1.1.tgz", - "integrity": "sha512-Lgmb0y4kv0ffsGMelAOaYOd4tYZAv4FYWgV86ckGMjmYQWA8drv4v/lHTNltixxWMmBEpjcHALpJuS6yAZYHug==", - "requires": { - "@chakra-ui/utils": "1.9.1" - } + "@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/react-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-1.2.1.tgz", - "integrity": "sha512-bV8FRaXiOgGxOg03iTNin/B02I+tHH9PQtqUTl3U7cJaoI+5AUYhrqXvl1Ya2/R7zxSFrb/gBVDTgbZiVkJ+Dg==", + "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/utils": "^1.9.1" + "@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/select": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-1.2.3.tgz", - "integrity": "sha512-LGT8z06InjfMTQyWgQUqinucvObkO0bOR87c4swWlAESkS6w+g8PsH9UJ87NT5W+hzCybvqSfRjMHYwC9qUnrg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", "requires": { - "@chakra-ui/form-control": "1.5.3", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" } }, + "@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": "1.2.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-1.2.5.tgz", - "integrity": "sha512-lZZ2HHOC+44/fS8+Ml0+2twgxogrsRH0Jh7WLJTkDw5UPhMj0mmB7mGEWYm32RiHo4GDeFj9slI+UJWIgBljMQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz", + "integrity": "sha512-JNRuMPpdZGd6zFVKjVQ0iusu3tXAdI29n4ZENYwAJEMf/fN0l12sVeirOxkJ7oEL0yOx2AgEYFSKdbcAgfUsAQ==", "requires": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/media-query": "1.2.3", - "@chakra-ui/system": "1.9.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, + "@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": "1.5.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-1.5.3.tgz", - "integrity": "sha512-i14b8MYlantiACI4jyjxU5PdX3Nwmz65TdINd7LywrKZu0ahE4GRXCcIGyM48vlXWfdkFhKLqLuF7+EYwLYtHA==", + "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.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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/spinner": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-1.2.1.tgz", - "integrity": "sha512-CQsUJNJWWSot1ku5Se41Nz1dXIDhk+/7FIhTbfRHSjtYZnAab3CPMHBkTGqwbJxQ9oHYgk9Rso3cfG+/ra6aTQ==", + "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/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/stat": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-1.2.2.tgz", - "integrity": "sha512-0StsPDC56MjzhdlBl0R8wU0uwj9L1tvhQzge/ELSDn4tQDI7VovrxpFzVH0qsj7EZDwZa0BRQaSrstzWvgmJ/Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.1.1.tgz", + "integrity": "sha512-LDn0d/LXQNbAn2KaR3F1zivsZCewY4Jsy1qShmfBMKwn6rI8yVlbvu6SiA3OpHS0FhxbsZxQI6HefEoIgtqY6Q==", + "requires": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "@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": "2.0.0", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/styled-system": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-1.16.0.tgz", - "integrity": "sha512-5vGU4eCixFMXp9u9PT+5724ptzBLP5AcFks88h19TpIevcElCkvAXw2G2CDJ0jJZM59l61nJGHIjn246f3iJUA==", + "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/utils": "1.9.1", - "csstype": "^3.0.9" + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" } }, "@chakra-ui/switch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-1.3.2.tgz", - "integrity": "sha512-aqhAq93DW97qD/KTBiWe2ip64vxSkN/qimMRFdG4xqtS2DxGeicQiv7Eis3NgAq2xIMM9XOoeXutU4kGkha1+w==", + "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/checkbox": "1.6.2", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/system": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-1.9.1.tgz", - "integrity": "sha512-zO/PIsosJalC58eh0vFeQGTigUpp9iHLFgUQDjs+STdreXGqYZdpRCYxriMt0ZBHNL+N8arHqlzXOlZxSZk1sA==", + "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/color-mode": "1.3.3", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/styled-system": "1.16.0", - "@chakra-ui/utils": "1.9.1", - "react-fast-compare": "3.2.0" + "@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.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + "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/table": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-1.3.1.tgz", - "integrity": "sha512-+ia/7zs7AGj01lon301EEx+mK4918yGc0K6e68Kxomex8tnxkwbskFWs6hX+6Kzbj56ZBm99eLlKpo2iGYX0HA==", + "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.9.1" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/tabs": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-1.6.2.tgz", - "integrity": "sha512-J0OY4sEhZW0jxlj4MkotLiD0/snMk7IsqvDxUB17MxuFRs5ytUssHzCqO7fexH7fSfd54ITNoUIns6Ir2ueCsA==", + "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/clickable": "1.2.1", - "@chakra-ui/descendant": "2.1.1", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.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/tag": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-1.2.2.tgz", - "integrity": "sha512-H25y9nEyUAUdwQDND9P4mMXKf1wf9UH4A3DyP237qVKIyYBpa4aCH8eJU4dunh2yIzASB0DWcr7lsul/HAHxmg==", + "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/icon": "2.0.0", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" } }, "@chakra-ui/textarea": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-1.2.3.tgz", - "integrity": "sha512-yLObhuSnIX11GyjsiEIC2gFTDtUoGSpHUqHRD8nF9BmuePHqFzG3jdkBrFMd09u0Ee6kr6o0tia6g9Wt29TyQQ==", + "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/form-control": "1.5.3", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" } }, "@chakra-ui/theme": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-1.12.3.tgz", - "integrity": "sha512-aqMGSfQLYOrcWBKaD5GuVYrpI5Zoq4Gt3D9P+drB/CDTRgBLWuJI1rbKOSSSJUK4RNZ0WbakPwz6wXiVS3vVdQ==", + "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/anatomy": "1.2.1", - "@chakra-ui/theme-tools": "1.3.1", - "@chakra-ui/utils": "1.9.1" + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" } }, "@chakra-ui/theme-tools": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-1.3.1.tgz", - "integrity": "sha512-D8arJ5uFGuYZrrFGpXqgov8FhsJYWRyar5oBZY5TJR9gsVYBlJ8Ai91pwM/NflCFqzerTOgyt7bNSGQMdZ8ghA==", + "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/utils": "1.9.1", - "@ctrl/tinycolor": "^3.4.0" + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" } }, - "@chakra-ui/toast": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-1.5.1.tgz", - "integrity": "sha512-cynOM/mGqf/dPulYOQMcpQnbf8pQWV2SEz1ymW1Nni1hh6zCqW6+pI+YeMxeadJLAwohnRumwzLsRiSL4l+YRQ==", + "@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/alert": "1.3.2", - "@chakra-ui/close-button": "1.2.2", - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/theme": "1.12.3", - "@chakra-ui/transition": "1.4.2", - "@chakra-ui/utils": "1.9.1", - "@reach/alert": "0.13.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/tooltip": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-1.4.3.tgz", - "integrity": "sha512-d+JsT65LPYFoGEfmqvqa3xDzW1enBdAHns3GYoHnie92pLseMKQYJsITO5SmwNGnEiOHbJ8pkB/hggo+xHKkpg==", + "@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==", "requires": { - "@chakra-ui/hooks": "1.7.2", - "@chakra-ui/popper": "2.4.1", - "@chakra-ui/portal": "1.3.2", - "@chakra-ui/react-utils": "1.2.1", - "@chakra-ui/utils": "1.9.1", - "@chakra-ui/visually-hidden": "1.1.1" + "@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/transition": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-1.4.2.tgz", - "integrity": "sha512-S+BNmpErHlntl//uaqv0sJegzMsQms0OnJapeZaRsvZL4s1SVYrR8kMrXigkdpeh4lAUqGsLpQHPKkzaKGbBOw==", + "@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==", "requires": { - "@chakra-ui/utils": "1.9.1" + "@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/utils": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-1.9.1.tgz", - "integrity": "sha512-Tue8JfpzOqeHd8vSqAnX1l/Y3Gg456+BXFP/TH6mCIeqMAMbrvv25vDskds0wlXRjMYdmpqHxCEzkalFrscGHA==", + "@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==", "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.1.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-1.1.1.tgz", - "integrity": "sha512-AGK9YBQS2FW/1e5tfivS8VVXn8y2uTyJ9ACOnGiLm9FNdth9pR0fGil9axlcmhZpEYcSRlnCuma3nkqaCjJnAA==", - "requires": { - "@chakra-ui/utils": "1.9.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", @@ -23950,11 +25922,6 @@ "minimist": "^1.2.0" } }, - "@ctrl/tinycolor": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", - "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==" - }, "@discoveryjs/json-ext": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", @@ -23962,50 +25929,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", @@ -24015,21 +25959,28 @@ } }, "@emotion/cache": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", - "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "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.1.0", - "@emotion/utils": "^1.0.0", - "@emotion/weak-memoize": "^0.2.5", - "stylis": "4.0.13" + "@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", @@ -24043,92 +25994,277 @@ "@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.7.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz", - "integrity": "sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==", - "requires": { - "@babel/runtime": "^7.13.10", - "@emotion/cache": "^11.7.1", - "@emotion/serialize": "^1.0.2", - "@emotion/sheet": "^1.1.0", - "@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.1.0", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", - "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + "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.6.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.6.0.tgz", - "integrity": "sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==", + "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.1", - "@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.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz", - "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==", + "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.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "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 } } @@ -24188,70 +26324,75 @@ } } }, + "@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.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.1.tgz", - "integrity": "sha512-Q240kcUszhXiAYudjuJgNuLgy9CryDP3wp83NOZQezfA6h3ByYKU7xI6DiKrdjyVaGpYN3ppUmdj0uf5GaXzMA==", + "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/utils": "^8.5.1", - "tslib": "~2.3.0" + "@graphql-tools/utils": "8.10.0", + "tslib": "^2.4.0" }, "dependencies": { "@graphql-tools/utils": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.5.3.tgz", - "integrity": "sha512-HDNGWFVa8QQkoQB0H1lftvaO1X5xUaUDk1zr1qDe0xN1NL0E/CrQdJ5UKLqOvH4hkqVUPxQsyOoAZFkaH6rLHg==", + "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.3.0" + "tslib": "^2.4.0" } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true } } }, "@graphql-tools/mock": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.5.1.tgz", - "integrity": "sha512-cwwqGs9Rofev1JdMheAseqM/rw1uw4CYb35vv3Kcv2bbyiPF+490xdlHqFeIazceotMFxC60LlQztwb64rsEnw==", + "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/schema": "^8.3.1", - "@graphql-tools/utils": "^8.6.0", + "@graphql-tools/schema": "9.0.1", + "@graphql-tools/utils": "8.10.0", "fast-json-stable-stringify": "^2.1.0", - "tslib": "~2.3.0" + "tslib": "^2.4.0" }, "dependencies": { "@graphql-tools/schema": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.1.tgz", - "integrity": "sha512-3R0AJFe715p4GwF067G5i0KCr/XIdvSfDLvTLEiTDQ8V/hwbOHEKHKWlEBHGRQwkG5lwFQlW1aOn7VnlPERnWQ==", + "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.2.1", - "@graphql-tools/utils": "^8.5.1", - "tslib": "~2.3.0", + "@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.6.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.1.tgz", - "integrity": "sha512-uxcfHCocp4ENoIiovPxUWZEHOnbXqj3ekWc0rm7fUhW93a1xheARNHcNKhwMTR+UKXVJbTFQdGI1Rl5XdyvDBg==", + "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.3.0" + "tslib": "^2.4.0" } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, "value-or-promise": { @@ -24263,71 +26404,59 @@ } }, "@graphql-tools/schema": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-7.1.5.tgz", - "integrity": "sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA==", + "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/utils": "^7.1.2", - "tslib": "~2.2.0", - "value-or-promise": "1.0.6" + "@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.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-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==" - }, - "@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==", - "dev": true, - "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==", - "dev": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } - } + "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": {} }, "@humanwhocodes/config-array": { "version": "0.5.0", @@ -24363,6 +26492,71 @@ "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": { + "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", @@ -24729,6 +26923,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", @@ -24866,15 +27069,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", @@ -24932,136 +27126,244 @@ "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==", "dev": true }, - "@lingui/babel-plugin-extract-messages": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.13.0.tgz", - "integrity": "sha512-akDiMq+CrF3m4ENA3DlEj+XfskZow6SqvrkOUVIStow5kUqcCBow635W7+YAem2TJNxH+CpVgpGV24osiQx+ZQ==", + "@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==", + "devOptional": true + }, + "@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.13.0", - "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.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@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==", + "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.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "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.13.0", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.13.0.tgz", - "integrity": "sha512-hK/7z+hqxT9CSzUQUQEefurbjmZCJldLG9kbSp8mNgJ+XLAv1mWPve79pYCbtMK7M7vbyU4uG0ncnH+pHKFF/w==", - "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.13.0", - "@lingui/conf": "^3.13.0", - "babel-plugin-macros": "^3.0.1", - "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.6", - "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.13.0", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.13.0.tgz", - "integrity": "sha512-1vl7NEZWMuiM2JCqnvlGmoyqlwB4isSEZrzvKWGAGMRLxMuuKR6PrH1Khgl4x2WRLZxfEysXTe6YR08Ra68irQ==", - "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.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25071,7 +27373,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" } @@ -25080,110 +27382,466 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "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==", + "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==", + "devOptional": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@lingui/core": { + "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": { + "@lingui/conf": "5.9.3", + "@lingui/message-utils": "5.9.3", + "date-fns": "^3.6.0", + "pofile": "^1.1.4" + } + }, + "@lingui/loader": { + "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.20.13", + "@lingui/cli": "5.9.3", + "@lingui/conf": "5.9.3" + } + }, + "@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, + "requires": { + "@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": "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": { + "moo": "^0.5.1" + } + }, + "@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.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" + } + }, + "@peculiar/asn1-cms": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.7.0.tgz", + "integrity": "sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==", + "dev": true, + "requires": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "@peculiar/asn1-x509-attr": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@peculiar/asn1-csr": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.7.0.tgz", + "integrity": "sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==", + "dev": true, + "requires": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } + }, + "@peculiar/asn1-ecc": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.7.0.tgz", + "integrity": "sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==", + "dev": true, + "requires": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "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==", + } + } + }, + "@peculiar/asn1-pfx": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.7.0.tgz", + "integrity": "sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==", + "dev": true, + "requires": { + "@peculiar/asn1-cms": "^2.7.0", + "@peculiar/asn1-pkcs8": "^2.7.0", + "@peculiar/asn1-rsa": "^2.7.0", + "@peculiar/asn1-schema": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "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.13.0", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.13.0.tgz", - "integrity": "sha512-UDmI8UL59rLmQDDjBK8JFMX0+i3+pncl3fWG+tD2cXNJkN+MEBrhECTQ2lsM1tCk09AfiATglPPXm1e0tLxxOw==", + "@peculiar/asn1-pkcs8": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.7.0.tgz", + "integrity": "sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==", + "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "make-plural": "^6.2.2", - "messageformat-parser": "^4.1.3" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@lingui/loader": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/loader/-/loader-3.13.0.tgz", - "integrity": "sha512-MuLwM6vVMYH/ilCGunNGIPTvZU9gtjaohU54/bDCRbDJ5teN46UKqFTYqP6TwVWa+AF6hRMcgUprBcQJi7Iw8Q==", + "@peculiar/asn1-pkcs9": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.7.0.tgz", + "integrity": "sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/cli": "^3.13.0", - "@lingui/conf": "^3.13.0", - "loader-utils": "^2.0.0", - "ramda": "^0.27.1" + "@peculiar/asn1-cms": "^2.7.0", + "@peculiar/asn1-pfx": "^2.7.0", + "@peculiar/asn1-pkcs8": "^2.7.0", + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "@peculiar/asn1-x509-attr": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@lingui/macro": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.13.0.tgz", - "integrity": "sha512-TmwAiFnxtutDEKp7KFtUmq5vIfv56zn0FV0ZgrISUcW1liVlRyqW6YnQ7cv4AzsPnkBhO2+O2YVoHY1r5owMvA==", + "@peculiar/asn1-rsa": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.7.0.tgz", + "integrity": "sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==", "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/conf": "^3.13.0", - "ramda": "^0.27.1" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@lingui/react": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lingui/react/-/react-3.13.0.tgz", - "integrity": "sha512-FS5NqWHh8Ngj1jnF9Yg+lqnIaMT0SjPBzOT6MpiO36tsWNFAdehqM589utvBmaU0eeV+/CyTF02GH6rd4PjMBg==", + "@peculiar/asn1-schema": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz", + "integrity": "sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==", + "dev": true, "requires": { - "@babel/runtime": "^7.11.2", - "@lingui/core": "^3.13.0" + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@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==", + "@peculiar/asn1-x509": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.7.0.tgz", + "integrity": "sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==", "dev": true, - "optional": true + "requires": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } + } }, - "@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==", + "@peculiar/asn1-x509-attr": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.7.0.tgz", + "integrity": "sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/asn1-x509": "^2.7.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "@nodelib/fs.stat": { + "@peculiar/utils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "requires": { + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "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==", + "@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, "@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==" + "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": { @@ -25201,13 +27859,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", @@ -25217,89 +27875,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=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "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.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - } - } - }, - "@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.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - } - } - }, - "@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.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - } - } + "@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==" }, - "@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", @@ -25319,29 +27931,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.31.2", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", - "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==", + "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.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.6", + "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" + "pretty-format": "^27.0.2" }, "dependencies": { "ansi-styles": { @@ -25384,6 +27987,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", @@ -25396,16 +28024,16 @@ } }, "@testing-library/jest-dom": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.1.tgz", - "integrity": "sha512-ajUJdfDIuTCadB79ukO+0l8O+QwN0LiSxDaYUTI4LndbbUsGi6rWU1SCexXzBA2NSjlVB9/vbkasQIL3tmPBjw==", + "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": "^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", @@ -25421,12 +28049,6 @@ "color-convert": "^2.0.1" } }, - "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 - }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -25470,13 +28092,14 @@ } }, "@testing-library/react": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", - "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", + "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.5", - "@testing-library/dom": "^7.28.1" + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" } }, "@testing-library/react-hooks": { @@ -25501,6 +28124,12 @@ "@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", @@ -25573,6 +28202,15 @@ "@types/node": "*" } }, + "@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -25582,41 +28220,48 @@ "@types/node": "*" } }, - "@types/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==", - "dev": true - }, - "@types/cookies": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.7.tgz", - "integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==", + "@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "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": { @@ -25625,17 +28270,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", @@ -25646,14 +28283,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": "*", @@ -25661,9 +28303,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": "*", @@ -25671,15 +28313,15 @@ } }, "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "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": "*", @@ -25689,9 +28331,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.26", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz", - "integrity": "sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==", + "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": "*", @@ -25699,14 +28341,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", @@ -25733,33 +28371,45 @@ "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, - "@types/http-assert": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.3.tgz", - "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==", - "dev": true - }, "@types/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true }, + "@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", @@ -25771,71 +28421,32 @@ } }, "@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==", + "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=", - "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==", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/koa": { - "version": "2.13.4", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.4.tgz", - "integrity": "sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==", - "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.178", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" }, - "@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==", + "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": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "@types/minimatch": { @@ -25848,7 +28459,7 @@ "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/normalize-package-data": { "version": "2.4.0", @@ -25895,9 +28506,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": "*" } @@ -25911,21 +28522,62 @@ "@types/react": "*" } }, + "@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "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/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==", + "@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/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "requires": { "@types/mime": "^1", "@types/node": "*" } }, + "@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "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", @@ -25970,11 +28622,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", @@ -26017,9 +28664,9 @@ } }, "@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "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": "*" @@ -26038,52 +28685,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 - }, - "@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": { @@ -26093,25 +28725,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" } }, @@ -26122,50 +28754,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": "2.4.0", - "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-2.4.0.tgz", - "integrity": "sha512-5QA1Std1HSfBjVObulMW6gi2ZlBg9zuP8jUpgs1LL5tLWritla4T6cheEGLNa6Yuuy/DKzpHJUmNGBtpBGFEGw==", + "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": "2.1.0", - "@visx/point": "2.1.0", - "@visx/scale": "2.2.2", - "@visx/shape": "2.4.0", - "@visx/text": "2.3.0", + "@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": "2.1.2", - "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-2.1.2.tgz", - "integrity": "sha512-O80K6PkDH//6xVDP3rSdd+9GNtBUMJKgVXh1ZjW8tAj/rtRq+GiyE17sB3uqV+btNkg0oJiVsFpmoLI50beJDQ==", + "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": "*", @@ -26173,52 +28894,93 @@ } }, "@visx/curve": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-2.1.0.tgz", - "integrity": "sha512-9b6JOnx91gmOQiSPhUOxdsvcnW88fgqfTPKoVgQxidMsD/I3wksixtwo8TR/vtEz2aHzzsEEhlv1qK7Y3yaSDw==", + "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": "2.1.2", - "resolved": "https://registry.npmjs.org/@visx/event/-/event-2.1.2.tgz", - "integrity": "sha512-x3gAQ9DB4zDA6qqGpzlpGacGuOtmzFi/m5Zq7BJ0OJ7PjNfkIazCsznc9epCT/g9IIhwhs+UN/Ijww4YnFHqHw==", + "@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": "2.1.0" + "prop-types": "^15.5.7" } }, "@visx/grid": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-2.4.0.tgz", - "integrity": "sha512-1tk8XaLeFRQA8BKy/+oP7aqsE99GKzyEYA1w4waga2VI0bDZiSnuZix6cy5PTusZxt9ZzjTmPGkC2RXq9+I5XA==", + "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": "2.1.0", - "@visx/group": "2.1.0", - "@visx/point": "2.1.0", - "@visx/scale": "2.2.2", - "@visx/shape": "2.4.0", + "@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": "2.1.0", - "resolved": "https://registry.npmjs.org/@visx/group/-/group-2.1.0.tgz", - "integrity": "sha512-bZKa54yVjGYPZZhzYHLz4AVlidSr4ET9B/xmSa7nnictMJWr7e/IuZThB/bMfDQlgdtvhcfTgs+ZluySc5SBUg==", + "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", @@ -26226,78 +28988,101 @@ } }, "@visx/legend": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@visx/legend/-/legend-2.2.2.tgz", - "integrity": "sha512-Pf77v3h12P5W2w5Iw1z+hucJVk19GOg+2R3N+O0SODDeaWg3pyN4eJMcs2LdYSfNWGkcejGu4G9Q/UUgjv7ihw==", + "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": "2.1.0", - "@visx/scale": "2.2.2", + "@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": "2.1.2", - "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz", - "integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==", + "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": "2.1.0", - "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.1.0.tgz", - "integrity": "sha512-vVnfI7oqjjttkn05Xi/ooR0UqQRoGf68lyT3SOl0WPHvIQBGNh3XoVUBHDr15/NUkfErgK6TNlfXY763YncPWg==" + "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": "2.4.1", - "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-2.4.1.tgz", - "integrity": "sha512-x5zPUmmsdVX/QaeDj9rLg3mGtNuiomBWUvwBo5oQs2KOtAFiviHx0NpLfY09h+p5gvkmGfTM+8gHEtVtIwLXPg==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-2.17.0.tgz", + "integrity": "sha512-3dY2shGbQnoknIRv3Vfnwsy3ZA8Q5Q/rYnTLiokWChYRfNC8NMPoX9mprEeb/gMAxtKjaLn3zcCgd8R+eetxIQ==", "requires": { + "@juggle/resize-observer": "^3.3.1", "@types/lodash": "^4.14.172", "@types/react": "*", "lodash": "^4.17.21", - "prop-types": "^15.6.1", - "resize-observer-polyfill": "1.5.1" + "prop-types": "^15.6.1" } }, "@visx/scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-2.2.2.tgz", - "integrity": "sha512-3aDySGUTpe6VykDQmF+g2nz5paFu9iSPTcCOEgkcru0/v5tmGzUdvivy8CkYbr87HN73V/Jc53lGm+kJUQcLBw==", + "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": "2.4.0", - "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-2.4.0.tgz", - "integrity": "sha512-D6XdGCgWi0/0ZKJ5iK8W5gILCKdrbDwnR/e7o6n/favLU0o+ntiI4a9PZBZ5bYS0aFNG7r+miGMcWV/AQfODuA==", + "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.172", "@types/react": "*", - "@visx/curve": "2.1.0", - "@visx/group": "2.1.0", - "@visx/scale": "2.2.2", + "@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", @@ -26305,20 +29090,22 @@ "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": "2.3.0", - "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.3.0.tgz", - "integrity": "sha512-5mEcmzWZqbziz6Azv+H6BWrnUbnpygAw4SNzBqF+YiGik5gR/USaBRAs9aKjnoUg6qFe3NZWFOcGpR3BzDR/bQ==", + "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.172", "@types/react": "*", @@ -26329,183 +29116,251 @@ } }, "@visx/tooltip": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-2.2.2.tgz", - "integrity": "sha512-wI0t+hE+gp7euuqI8likKBDsx70I+WJ6UJ9YCra8/LyyuVSbOobnLZlqQw1QhIYbdmnDReIckZ0dq3sa6z2qbw==", + "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": "2.1.2", + "@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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "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-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "@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==", + "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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "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-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==", + "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/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "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.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "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.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/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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "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.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/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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "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.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "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.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/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.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "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.11.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "@webpack-cli/configtest": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz", - "integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz", - "integrity": "sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "requires": {} }, "@webpack-cli/serve": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz", - "integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, "requires": {} }, @@ -26566,10 +29421,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": { @@ -26579,18 +29452,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.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "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": { @@ -26611,10 +29484,10 @@ } } }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "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": {} }, @@ -26631,6 +29504,32 @@ "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" + }, + "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": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -26643,11 +29542,34 @@ "uri-js": "^4.2.2" } }, - "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-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": { + "ajv": "^8.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": { + "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 + } + } }, "ajv-keywords": { "version": "3.5.2", @@ -26655,15 +29577,6 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "requires": { - "string-width": "^4.1.0" - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -26695,6 +29608,7 @@ "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" } @@ -26709,299 +29623,210 @@ "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.5", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.5.tgz", - "integrity": "sha512-RGt5k2JeBqrmnwRM0VOgWFiGKlGJMfmiif/4JvdaEqhMJ+xqe/9cfDYzXfn33ke2eWixsAbjEbRfy8XbaN9nTw==", + "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": { - "core-js-pure": "^3.10.2", - "lodash.sortby": "^4.7.0", - "sha.js": "^2.4.11" + "@apollo/utils.keyvaluecache": "^1.0.1", + "apollo-server-env": "^4.2.1" } }, - "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==", + "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==", "dev": true, "requires": { - "apollo-utilities": "^1.3.0", - "ts-invariant": "^0.4.0", - "tslib": "^1.9.3", - "zen-observable-ts": "^0.8.21" + "@apollo/protobufjs": "1.2.6" }, "dependencies": { - "ts-invariant": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", - "integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==", + "@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": { - "tslib": "^1.9.3" + "@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" } + }, + "@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-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.25.3", - "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.25.3.tgz", - "integrity": "sha512-+eUY2//DLkU7RkJLn6CTl1P89/ZMHuUQnWqv8La2iJ2hLT7Me+nMx+hgHl3LqlT/qDstQ8qA45T85FuCayplmQ==", - "dev": true, - "requires": { - "apollo-server-core": "^2.25.3", - "apollo-server-express": "^2.25.3", - "express": "^4.0.0", - "graphql-subscriptions": "^1.0.0", - "graphql-tools": "^4.0.8", - "stoppable": "^1.1.0" - } - }, - "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==", + "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.3", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.25.3.tgz", - "integrity": "sha512-Midow3uZoJ9TjFNeCNSiWElTVZlvmB7G7tG6PPoxIR9Px90/v16Q6EzunDIO0rTJHRC3+yCwZkwtf8w2AcP0sA==", - "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": { - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "dev": true + "@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": { + "@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" + } }, - "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==", + "@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": { - "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" + "tslib": "^2.4.0" } }, - "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==", + "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.3", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.25.3.tgz", - "integrity": "sha512-tTFYn0oKH2qqLwVj7Ez2+MiKleXACODiGh5IxsB7VuYCPMAi9Yl8iUSlwTjQUvgCWfReZjnf0vFL2k5YhDlrtQ==", + "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.3", - "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": { - "@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==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "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==", - "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" - } - }, - "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==", - "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", @@ -27012,22 +29837,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", @@ -27053,14 +29881,14 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", "get-intrinsic": "^1.1.1", "is-string": "^1.0.7" } @@ -27098,37 +29926,42 @@ } }, "array.prototype.flatmap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", - "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", + "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.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" } }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, "requires": { - "safer-buffer": "~2.1.0" + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -27141,27 +29974,6 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "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==", - "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==", - "dev": true - }, "async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -27169,14 +29981,6 @@ "dev": true, "requires": { "retry": "0.13.1" - }, - "dependencies": { - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true - } } }, "asynckit": { @@ -27185,29 +29989,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", @@ -27277,37 +30072,15 @@ } }, "babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", + "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": { @@ -27348,7 +30121,6 @@ "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", @@ -27356,41 +30128,41 @@ } }, "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==", + "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/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.0", + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", "semver": "^6.1.1" }, "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 } } }, "babel-plugin-polyfill-corejs3": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.0.tgz", - "integrity": "sha512-Hcrgnmkf+4JTj73GbK3bBhlVPiLL47owUAnoJIf69Hakl3q+KfodbDXiZWGMM7iqCZTxCG3Z2VRfPNYES4rXqQ==", + "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.0", - "core-js-compat": "^3.20.0" + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.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==", + "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.0" + "@babel/helper-define-polyfill-provider": "^0.3.2" } }, "babel-preset-current-node-syntax": { @@ -27423,11 +30195,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", @@ -27494,32 +30261,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", @@ -27527,9 +30280,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": { @@ -27552,138 +30305,73 @@ } }, "body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "requires": { - "bytes": "3.1.1", - "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.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "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 - } - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" + "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.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.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" - } + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, - "color-convert": { + "http-errors": { "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, + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "requires": { - "has-flag": "^4.0.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.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 + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "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" - } + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, + "bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "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", @@ -27691,12 +30379,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": { @@ -27706,15 +30394,16 @@ "dev": true }, "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", - "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "devOptional": true, + "requires": { + "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": { @@ -27739,7 +30428,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": { @@ -27748,25 +30437,25 @@ "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==", + "bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, "requires": { - "dicer": "0.3.0" + "run-applescript": "^7.0.0" } }, "bytes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true }, "cache-base": { "version": "1.0.1", @@ -27785,46 +30474,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": { @@ -27854,12 +30531,13 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", - "dev": true + "devOptional": true }, "caniuse-lite": { - "version": "1.0.30001298", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001298.tgz", - "integrity": "sha512-AcKqikjMLlvghZL/vfTHorlQsLDhGRalYf1+GmWCf5SCMziSGjRYQW/JEksj14NaYHIR6KIhrFAy0HV5C25UzQ==" + "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", @@ -27870,16 +30548,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", @@ -27892,12 +30565,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", @@ -27914,21 +30581,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", @@ -28016,12 +30668,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", @@ -28038,20 +30684,14 @@ "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==", + "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" + "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", @@ -28080,15 +30720,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", @@ -28115,6 +30746,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" } @@ -28122,7 +30754,13 @@ "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": "2.0.16", @@ -28131,7 +30769,8 @@ "dev": true }, "colors": { - "version": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true }, @@ -28145,9 +30784,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": { @@ -28166,38 +30805,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", @@ -28205,24 +30846,10 @@ "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==", + "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": { @@ -28241,9 +30868,9 @@ } }, "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", @@ -28254,9 +30881,9 @@ } }, "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + "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", @@ -28264,9 +30891,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "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": { @@ -28276,36 +30903,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.20.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.2.tgz", - "integrity": "sha512-qZEzVQ+5Qh6cROaTPFLNS4lkvQ6mBzE3R6A6EEpssj7Zr2egMHgsy4XapdifqJDGC9CBiNv7s+ejI96rLNQFdg==", + "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.19.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", @@ -28327,7 +31008,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", @@ -28336,16 +31016,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", @@ -28353,37 +31027,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", @@ -28420,7 +31063,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": { @@ -28447,56 +31090,75 @@ } }, "csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "d3": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-6.7.0.tgz", - "integrity": "sha512-hNHRhe+yCDLUG6Q2LwvR/WdNFPOJQ5VWqsJcwIYVeI401+d2/rrCjxSXkiAdIlpx7/73eApFB4Olsmh3YN7a6g==", - "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" + } } } }, @@ -28506,129 +31168,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": { @@ -28637,52 +31317,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": { @@ -28694,49 +31384,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": { @@ -28751,9 +31432,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": { @@ -28782,39 +31463,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", @@ -28827,16 +31489,22 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", "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==", + "default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" } }, + "default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true + }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -28846,19 +31514,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": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "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": { @@ -28918,9 +31598,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", @@ -28931,18 +31614,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", @@ -28962,30 +31640,15 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "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" } }, - "dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", - "dev": true, - "requires": { - "streamsearch": "0.1.2" - } - }, - "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", @@ -29001,30 +31664,19 @@ "path-type": "^4.0.0" } }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, "dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "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", @@ -29128,36 +31780,33 @@ } } }, - "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==" + "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", @@ -29173,9 +31822,10 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.4.41", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.41.tgz", - "integrity": "sha512-VQEXEJc+8rJIva85H8EPtB5Ux9g8TzkNGBanqphM9ZWMZ34elueKJ+5g+BPhz3Lk8gkujfQRcIZ+fpA0btUIuw==" + "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", @@ -29210,13 +31860,13 @@ } }, "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "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.2.0" + "tapable": "^2.3.0" } }, "enquirer": { @@ -29235,20 +31885,11 @@ "dev": true }, "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "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", @@ -29265,39 +31906,81 @@ } }, "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "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", + "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.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "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.1" + "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": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "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": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -29309,16 +31992,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", @@ -29328,7 +32039,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", @@ -29489,9 +32201,9 @@ "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" @@ -29537,10 +32249,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", @@ -29560,16 +32275,9 @@ } }, "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==", + "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": {} }, @@ -29597,24 +32305,13 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true - }, - "resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", - "dev": true, - "requires": { - "is-core-module": "^2.8.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } } } }, "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==", + "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": "^3.2.7", @@ -29681,20 +32378,10 @@ } } }, - "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.25.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", - "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "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", @@ -29702,14 +32389,14 @@ "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.2", + "eslint-module-utils": "^2.7.3", "has": "^1.0.3", - "is-core-module": "^2.8.0", + "is-core-module": "^2.8.1", "is-glob": "^4.0.3", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "object.values": "^1.1.5", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.12.0" + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" }, "dependencies": { "doctrine": { @@ -29720,77 +32407,38 @@ "requires": { "esutils": "^2.0.2" } - }, - "resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", - "dev": true, - "requires": { - "is-core-module": "^2.8.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } } } }, "eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", - "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.28.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", - "integrity": "sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==", + "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.4", - "array.prototype.flatmap": "^1.2.5", + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", "doctrine": "^2.1.0", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "object.entries": "^1.1.5", "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.0", + "object.hasown": "^1.1.1", "object.values": "^1.1.5", - "prop-types": "^15.7.2", + "prop-types": "^15.8.1", "resolve": "^2.0.0-next.3", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.6" + "string.prototype.matchall": "^4.0.7" }, "dependencies": { "doctrine": { @@ -29819,24 +32467,17 @@ } }, "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 } } }, "eslint-plugin-react-hooks": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", - "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", - "dev": true, - "requires": {} - }, - "eslint-plugin-standard": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz", - "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==", + "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": {} }, @@ -29848,25 +32489,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": { @@ -29884,6 +32506,13 @@ "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.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -29926,6 +32555,23 @@ } } }, + "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 + } + } + }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", @@ -29941,12 +32587,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": { @@ -29955,15 +32601,6 @@ "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, - "eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", - "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", @@ -29986,9 +32623,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", @@ -30112,55 +32749,65 @@ } }, "express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.1", - "content-disposition": "0.5.4", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.1", - "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", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.9.6", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "~1.5.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", @@ -30182,17 +32829,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", @@ -30258,12 +32894,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", @@ -30273,20 +32903,20 @@ "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": { @@ -30307,6 +32937,12 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true + }, "fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -30314,9 +32950,9 @@ "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" @@ -30340,15 +32976,6 @@ "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": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -30359,26 +32986,38 @@ } }, "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" } }, "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": { @@ -30442,15 +33081,6 @@ "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": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -30473,39 +33103,39 @@ } }, "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, "focus-lock": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.9.2.tgz", - "integrity": "sha512-YtHxjX7a0IC0ZACL5wsX8QdncXofWpGPNoVMuI/nZUrPGp6LmNI6+D5j0pPj+v8Kw5EpweA+T5yImK0rnWf7oQ==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", + "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", "requires": { "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==" + "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": { @@ -30514,41 +33144,47 @@ "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": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", "dev": true, "requires": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" - }, - "dependencies": { - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true - } + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" } }, "formik": { @@ -30617,25 +33253,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "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 - }, - "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" - } + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-readdir-recursive": { "version": "1.1.0", @@ -30657,9 +33275,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", @@ -30667,16 +33297,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", @@ -30685,14 +33316,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": { @@ -30706,6 +33343,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", @@ -30731,25 +33377,16 @@ "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" } @@ -30779,19 +33416,11 @@ "process": "^0.11.10" } }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true }, "globby": { "version": "6.1.0", @@ -30814,58 +33443,33 @@ } } }, - "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.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "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.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", - "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==" - }, - "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.9.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.9.0.tgz", - "integrity": "sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==", + "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": {} }, "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" @@ -30886,27 +33490,6 @@ } } }, - "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" - }, - "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 - } - } - }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -30920,22 +33503,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", @@ -30945,29 +33512,38 @@ } }, "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-symbols": { + "has-property-descriptors": { "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-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.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "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.2" + "has-symbols": "^1.0.3" } }, "has-value": { @@ -31022,11 +33598,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", @@ -31034,30 +33612,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true - }, "hey-listen": { "version": "1.0.8", "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", @@ -31125,12 +33684,6 @@ "whatwg-encoding": "^1.0.5" } }, - "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==", - "dev": true - }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -31185,180 +33738,124 @@ "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", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - } - } - }, - "http-parser-js": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", - "integrity": "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==", - "dev": true - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "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==", - "dev": true, - "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "dependencies": { - "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" - } - } - } + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "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==" }, - "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" - } + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "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=", + "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", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "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" + }, + "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": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "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 } } }, - "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": { @@ -31367,6 +33864,12 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -31382,9 +33885,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": { @@ -31394,20 +33897,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", @@ -31491,94 +33988,6 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "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.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -31590,12 +33999,28 @@ "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==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "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", @@ -31604,29 +34029,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", @@ -31647,50 +34054,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": { @@ -31700,9 +34089,9 @@ "dev": 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==", + "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": { @@ -31748,12 +34137,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", @@ -31813,20 +34196,19 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" + "is-docker": "^3.0.0" }, "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==", + "is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true } } @@ -31837,16 +34219,21 @@ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "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-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 }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", "dev": true }, "is-number": { @@ -31856,15 +34243,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": { @@ -31891,6 +34281,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", @@ -31901,9 +34297,9 @@ } }, "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": { @@ -31917,10 +34313,13 @@ } }, "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 + "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": { "version": "1.1.0", @@ -31946,6 +34345,15 @@ "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", @@ -31953,12 +34361,12 @@ "dev": true }, "is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "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.0" + "call-bind": "^1.0.2" } }, "is-windows": { @@ -31977,16 +34385,11 @@ "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", @@ -32009,12 +34412,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", @@ -32034,9 +34431,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 } } @@ -32116,7 +34513,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", @@ -32475,55 +34882,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", @@ -33256,9 +35618,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" @@ -33472,9 +35834,9 @@ } }, "jest-worker": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.2.tgz", - "integrity": "sha512-0QMy/zPovLfUPyHuOuuU4E+kGACXXE84nRnq6lBVI9GJg5DCBiA97SATi+ZP8CpiJwEQy1oCPjRBf8AnLjN+Ag==", + "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": "*", @@ -33499,35 +35861,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", @@ -33535,12 +35902,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", @@ -33550,31 +35918,43 @@ "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": { - "version": "2.5.2", - "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 + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==" }, - "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.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "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", @@ -33588,52 +35968,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": { @@ -33641,21 +35991,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.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "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", @@ -33667,41 +36014,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", @@ -33714,20 +36046,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", @@ -33745,15 +36078,15 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "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==", + "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", @@ -33761,78 +36094,21 @@ "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.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + "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", @@ -33849,12 +36125,6 @@ "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", @@ -33863,7 +36133,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": { @@ -33938,9 +36208,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": { @@ -33974,12 +36244,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", @@ -34005,23 +36269,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", @@ -34048,66 +36306,288 @@ } }, "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=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "isarray": { + "memfs": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz", + "integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==", + "dev": true, + "requires": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-to-fsa": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "dependencies": { + "@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "requires": {} + }, + "@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "requires": {} + }, + "@jsonjoy.com/codegen": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "requires": {} }, - "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==", + "@jsonjoy.com/fs-core": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz", + "integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==", "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" + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" + } + }, + "@jsonjoy.com/fs-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz", + "integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==", + "dev": true, + "requires": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" } }, - "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==", + "@jsonjoy.com/fs-node": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz", + "integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==", + "dev": true, + "requires": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + } + }, + "@jsonjoy.com/fs-node-builtins": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz", + "integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==", + "dev": true, + "requires": {} + }, + "@jsonjoy.com/fs-node-to-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz", + "integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==", + "dev": true, + "requires": { + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2" + } + }, + "@jsonjoy.com/fs-node-utils": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz", + "integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==", + "dev": true, + "requires": { + "@jsonjoy.com/fs-node-builtins": "4.57.2" + } + }, + "@jsonjoy.com/fs-print": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz", + "integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==", + "dev": true, + "requires": { + "@jsonjoy.com/fs-node-utils": "4.57.2", + "tree-dump": "^1.1.0" + } + }, + "@jsonjoy.com/fs-snapshot": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz", + "integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==", + "dev": true, + "requires": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "dependencies": { + "@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "dev": true, + "requires": {} + }, + "@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "requires": {} + }, + "@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, + "requires": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + } + }, + "@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, + "requires": { + "@jsonjoy.com/util": "17.67.0" + } + }, + "@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "requires": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + } + } + } + }, + "@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "requires": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "dependencies": { + "@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "requires": {} + } + } + }, + "@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "requires": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + } + }, + "@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "requires": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "dependencies": { + "@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "requires": {} + } + } + }, + "glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "requires": {} + }, + "thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", + "dev": true, + "requires": {} + }, + "tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "requires": {} + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true } } }, "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", @@ -34121,24 +36601,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": { @@ -34147,16 +36622,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": { @@ -34165,16 +36640,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" @@ -34186,15 +36655,6 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, - "mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - } - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -34202,18 +36662,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", @@ -34236,11 +36703,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", @@ -34254,27 +36720,15 @@ "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", @@ -34306,9 +36760,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", @@ -34340,24 +36794,39 @@ } } }, - "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-int64": { @@ -34388,9 +36857,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": { @@ -34400,34 +36869,29 @@ } }, "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" + "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.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "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.5.2", "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "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 - }, "chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -34453,29 +36917,11 @@ "ms": "^2.1.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" - } - }, "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 - }, - "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" - } } } }, @@ -34506,12 +36952,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "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 - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -34544,12 +36984,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", @@ -34587,20 +37021,9 @@ } }, "object-inspect": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", - "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", - "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", @@ -34608,12 +37031,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "object-path": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", - "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", - "dev": true - }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -34657,25 +37074,14 @@ "es-abstract": "^1.19.1" } }, - "object.getownpropertydescriptors": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", - "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, "object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "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": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "object.pick": { @@ -34698,6 +37104,12 @@ "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", @@ -34705,18 +37117,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", @@ -34728,29 +37139,24 @@ } }, "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": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "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 - } + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" } }, "optimism": { @@ -34843,27 +37249,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", @@ -34885,15 +37270,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", @@ -34901,12 +37277,14 @@ "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": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "dev": true, "requires": { - "retry": "^0.12.0" + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" } }, "p-try": { @@ -34915,30 +37293,10 @@ "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" - }, - "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 - } - } - }, - "papaparse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.0.tgz", - "integrity": "sha512-Lb7jN/4bTpiuGPrYy4tkKoUS8sTki8zacB5ke1p5zolhcSE4TlWgrlsxjrDTbG/dFVh07ck7X36hUf/b5V68pg==", + "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": { @@ -34968,13 +37326,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" } }, @@ -35013,12 +37371,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", @@ -35048,31 +37400,43 @@ "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.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "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": { @@ -35105,34 +37469,38 @@ "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==", + "pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", "dev": true, "requires": { - "find-up": "^3.0.0" + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "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==", + "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.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": { @@ -35153,42 +37521,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", @@ -35196,22 +37532,22 @@ "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.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "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": { @@ -35320,19 +37656,13 @@ "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": { @@ -35363,127 +37693,117 @@ "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==", + "pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "dev": true, "requires": { - "escape-goat": "^2.0.0" + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + } } }, - "qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "dev": true }, + "qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "requires": { + "side-channel": "^1.1.0" + } + }, "querystringify": { "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.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", - "requires": { - "bytes": "3.1.1", - "http-errors": "1.8.1", - "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, + "raw-body": { + "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": { - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, - "strip-json-comments": { + "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": { @@ -35499,17 +37819,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.2", - "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.2.tgz", - "integrity": "sha512-WzpdOnEqjf+/A3EH9opMZWauag7gV0BxFl+EY4ElA4qFqYsUsBLnmo2sELbN5OC30S16GAWMy16B9DLPpdJKAQ==", + "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.9.1", + "focus-lock": "^1.3.5", "prop-types": "^15.6.2", - "react-clientside-effect": "^1.2.5", - "use-callback-ref": "^1.2.5", - "use-sidecar": "^1.0.5" + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.2", + "use-sidecar": "^1.1.2" } }, "react-hot-loader": { @@ -35529,18 +37887,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", @@ -35556,19 +37914,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", @@ -35576,9 +37962,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", @@ -35589,159 +37975,115 @@ } }, "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.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", - "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", - "requires": { - "@babel/runtime": "^7.12.13", - "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.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "requires": { + "@remix-run/router": "1.23.3" + } + }, "react-router-dom": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz", - "integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", "requires": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.1", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" } }, - "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.7.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz", - "integrity": "sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==", + "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" } }, "readable-stream": { @@ -35749,165 +38091,28 @@ "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, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "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" - } - } - } - }, - "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", - "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" - } - }, - "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" - } - }, - "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" - } - } + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "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==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "requires": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" } }, "redent": { @@ -35923,7 +38128,7 @@ "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=", + "integrity": "sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==", "requires": { "balanced-match": "^0.4.2", "math-expression-evaluator": "^1.2.14", @@ -35933,7 +38138,7 @@ "balanced-match": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + "integrity": "sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg==" } } }, @@ -35945,6 +38150,12 @@ "balanced-match": "^1.0.0" } }, + "reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -35952,23 +38163,23 @@ "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==", + "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": { "regenerate": "^1.4.2" } }, "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==" + "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.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "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" @@ -35985,63 +38196,46 @@ } }, "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "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" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "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==", + "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": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", + "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" } }, - "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==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", "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==", + "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": { "jsesc": "~0.5.0" @@ -36050,7 +38244,7 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", "dev": true } } @@ -36092,90 +38286,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "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==", - "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==", - "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==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "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==", - "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" - } - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -36200,18 +38310,14 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, - "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==" - }, "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "requires": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-cwd": { @@ -36236,26 +38342,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, - "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==" - }, "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 }, - "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", @@ -36273,9 +38365,9 @@ "dev": true }, "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true }, "reusify": { @@ -36293,37 +38385,36 @@ "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-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "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 + "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": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, - "rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "safe-buffer": { "version": "5.1.2", @@ -36494,6 +38585,14 @@ "xmlchars": "^2.2.0" } }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, "schema-utils": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", @@ -36505,6 +38604,16 @@ "ajv-keywords": "^3.4.1" } }, + "scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" + }, + "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==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -36512,73 +38621,58 @@ "dev": true }, "selfsigned": { - "version": "1.10.14", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", - "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "dev": true, "requires": { - "node-forge": "^0.10.0" + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "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" - }, - "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 - } - } - }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "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": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "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==" } } }, - "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, - "requires": { - "randombytes": "^2.1.0" - } - }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -36615,14 +38709,21 @@ } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.19.0" + }, + "dependencies": { + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + } } }, "set-blocking": { @@ -36631,6 +38732,20 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "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" + } + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -36660,13 +38775,28 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "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.1", - "safe-buffer": "^5.0.1" + "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 + }, + "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 + } } }, "shallow-clone": { @@ -36699,6 +38829,12 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shell-quote": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "dev": true + }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -36707,14 +38843,47 @@ "optional": 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, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "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": { @@ -36723,6 +38892,23 @@ "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": { + "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 + } + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -36893,59 +39079,14 @@ } }, "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "requires": { "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", + "uuid": "^8.3.2", "websocket-driver": "^0.7.4" - }, - "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 - } - } - }, - "sockjs-client": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", - "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==", - "dev": true, - "requires": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.3" - }, - "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" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "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": { @@ -36954,26 +39095,26 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "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 }, "source-map-loader": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-2.0.2.tgz", - "integrity": "sha512-yIYkFOsKn+OdOirRJUPQpnZiMkF74raDVQjj5ni3SzbOiA57SabeX80R5zyMQAKpvKySA3Z4a85vFX3bvpC6KQ==", + "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": { - "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" }, "dependencies": { "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, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -36981,16 +39122,6 @@ } } }, - "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, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -37061,12 +39192,12 @@ }, "dependencies": { "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, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -37092,12 +39223,12 @@ }, "dependencies": { "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, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -37123,23 +39254,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "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" - } - }, "stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", @@ -37181,24 +39295,7 @@ "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=", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, "string_decoder": { @@ -37228,8 +39325,19 @@ "strip-ansi": "^6.0.0" } }, - "string-width": { - "version": "4.2.3", + "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, @@ -37240,39 +39348,41 @@ } }, "string.prototype.matchall": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", - "integrity": "sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==", + "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.2", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", + "regexp.prototype.flags": "^1.4.1", "side-channel": "^1.0.4" } }, "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==", + "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": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "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==", + "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.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "strip-ansi": { @@ -37284,6 +39394,15 @@ "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": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -37334,73 +39453,38 @@ } }, "stylis": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", - "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" - }, - "subscriptions-transport-ws": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", - "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", - "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==" - } - } + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, "superagent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.0.2.tgz", - "integrity": "sha512-2Kx35bZxLLJMBKtuXezxvD0aZQ7l923VwoCn7EtUx+aFxdG7co7PeRIddfrNtvvMuGaLZXA0mKzX+yWRhjrJ7A==", + "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": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.3", - "debug": "^4.3.3", + "debug": "^4.3.4", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", + "form-data": "4.0.4", "formidable": "^2.0.1", "methods": "^1.1.2", - "mime": "^2.5.0", - "qs": "^6.10.1", + "mime": "2.6.0", + "qs": "^6.10.3", "readable-stream": "^3.6.0", - "semver": "^7.3.5" + "semver": "^7.3.7" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "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" } }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, "mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -37413,19 +39497,10 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "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==", + "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" @@ -37434,19 +39509,20 @@ } }, "supertest": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.1.tgz", - "integrity": "sha512-2kBKhfZgnPLmjpzB0n7A2ZnEAWTaLXq4bn3EEVY9w8rUpLyIlSusqKKvWA1Cav7hxXBnXGpxBsSeOHj5wQGe1Q==", + "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": { "methods": "^1.1.2", - "superagent": "^7.0.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" } @@ -37481,13 +39557,7 @@ "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 - }, - "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==" + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "symbol-observable": { "version": "4.0.0", @@ -37534,9 +39604,9 @@ } }, "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "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": { @@ -37550,13 +39620,14 @@ } }, "terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", "commander": "^2.20.0", - "source-map": "~0.7.2", "source-map-support": "~0.5.20" }, "dependencies": { @@ -37565,44 +39636,59 @@ "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.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.2.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.5.tgz", - "integrity": "sha512-3luOVHku5l0QBeYS8r4CdHYWEGMmIj3H1U64jgkdZzECcSOJAyJ9TjuqcQZvw1Y+4AOBN9SeYJPJmFn2cM4/2g==", + "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": { - "jest-worker": "^27.0.6", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "dependencies": { - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "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": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "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" + } + }, + "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" + } } } }, @@ -37623,18 +39709,42 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "threads": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/threads/-/threads-1.7.0.tgz", + "integrity": "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==", + "dev": true, + "requires": { + "callsites": "^3.1.0", + "debug": "^4.2.0", + "is-observable": "^2.1.0", + "observable-fns": "^0.6.1", + "tiny-worker": ">= 2" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "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 + } + } + }, "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", @@ -37651,13 +39761,39 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "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": { + "esm": "^3.2.25" + } + }, + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "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": { @@ -37666,10 +39802,24 @@ "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=" + "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": { + "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 + } + } }, "to-object-path": { "version": "0.3.0", @@ -37691,12 +39841,6 @@ } } }, - "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", @@ -37721,7 +39865,7 @@ "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==" }, "toidentifier": { "version": "1.0.1", @@ -37743,20 +39887,21 @@ } }, "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, "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "dependencies": { "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 } } @@ -37770,51 +39915,46 @@ "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.9.4", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.9.4.tgz", - "integrity": "sha512-63jtX/ZSwnUNi/WhXjnK8kz4cHHpYS60AnmA6ixz17l7E12a5puCWFlNpkne5Rl0J8TBPVHpGjsj4fxs8ObVLQ==", + "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": { "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" } } }, - "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.12.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", - "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "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.0", + "minimist": "^1.2.6", "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==", + "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" @@ -37828,29 +39968,23 @@ "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==", + "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" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "tslib": "^1.9.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", @@ -37881,6 +40015,17 @@ "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": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + } + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -37891,21 +40036,21 @@ } }, "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, "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==", + "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": { - "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" } }, @@ -37960,21 +40105,6 @@ "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", @@ -38026,92 +40156,14 @@ } } }, - "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": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "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": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "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": "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" - } - } + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, "uri-js": { @@ -38129,43 +40181,16 @@ "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.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", - "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "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": { "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", @@ -38173,18 +40198,34 @@ "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": {} + "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==" + } + } }, "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==", "requires": { "detect-node-es": "^1.1.0", - "tslib": "^1.9.3" + "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==" + } } }, "util-deprecate": { @@ -38193,19 +40234,6 @@ "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", @@ -38258,15 +40286,10 @@ "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==", + "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 }, "vary": { @@ -38274,17 +40297,6 @@ "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", @@ -38312,18 +40324,10 @@ "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.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -38355,636 +40359,316 @@ "dev": true }, "webpack": { - "version": "5.66.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", - "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", - "dev": 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", + "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.8.3", - "es-module-lexer": "^0.9.0", + "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.9", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.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", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", - "webpack-sources": "^3.2.2" - }, - "dependencies": { - "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, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "webpack-cli": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", - "integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.0", - "@webpack-cli/info": "^1.4.0", - "@webpack-cli/serve": "^1.6.0", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", - "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 - }, - "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" - } - } - } - }, - "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": { - "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.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz", - "integrity": "sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA==", - "dev": true, - "requires": { - "ansi-html-community": "0.0.8", - "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.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.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" + "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": { - "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=", - "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" - } - } + "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" } }, - "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": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + } + }, + "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": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "requires": { - "resolve-from": "^3.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" } }, - "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==", + "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==", + "dev": true + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "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" - } - } + "mime-db": "^1.54.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "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": { - "ansi-regex": "^2.0.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" } - }, - "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==", + } + } + }, + "webpack-dev-server": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz", + "integrity": "sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "dependencies": { + "@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^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=", + "@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "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==", + "ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "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" - } - } + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, - "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "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": { - "async-limiter": "~1.0.0" + "fast-deep-equal": "^3.1.3" } }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "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" + "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" } }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "dev": true + }, + "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": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.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.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "dev": true, + "requires": {} } } }, @@ -38999,9 +40683,9 @@ } }, "webpack-sources": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.2.tgz", - "integrity": "sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw==", + "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": { @@ -39075,13 +40759,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": { @@ -39091,9 +40781,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": { @@ -39133,6 +40823,43 @@ } } }, + "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" + }, + "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", @@ -39152,16 +40879,31 @@ } }, "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, "requires": {} }, - "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 + "wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "requires": { + "is-wsl": "^3.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "requires": { + "is-inside-container": "^1.0.0" + } + } + } }, "xml-name-validator": { "version": "3.0.0", @@ -39176,9 +40918,9 @@ "dev": true }, "xss": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.10.tgz", - "integrity": "sha512-qmoqrRksmzqSKvgqzN0055UFWY7OKx1/9JWeRswwEVX9fCG5jcYRxa/A2DHcmZX6VJvjzHRQ2STeeVcQkrmLSw==", + "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", @@ -39206,9 +40948,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", @@ -39283,12 +41025,6 @@ } } }, - "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.32.11", "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", @@ -39307,16 +41043,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 b15758ebbc..02f33b57bf 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_OPTIONS=--openssl-legacy-provider node index.js", - "test": "jest", + "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,100 +17,104 @@ "mocker": "nodemon --watch mocking/ --exec 'babel mocking/ -d ./dist/mocking && node dist/mocking/mocker.js'" }, "dependencies": { - "@apollo/client": "^3.5.7", - "@babel/runtime": "^7.16.7", - "@chakra-ui/icons": "^1.1.2", - "@chakra-ui/react": "^1.7.5", - "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@lingui/react": "^3.13.0", - "@visx/axis": "^2.4.0", - "@visx/event": "^2.1.2", - "@visx/grid": "^2.4.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.2.2", - "@visx/mock-data": "^2.1.2", - "@visx/responsive": "^2.4.1", - "@visx/scale": "^2.2.2", - "@visx/shape": "^2.4.0", - "@visx/tooltip": "^2.2.2", - "body-parser": "^1.19.1", - "d3": "^6.7.0", - "d3-scale": "^3.3.0", - "d3-selection": "^2.0.0", - "dotenv": "^8.6.0", - "express": "^4.17.2", - "fast-deep-equal": "^3.1.3", + "@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.5", + "compression": "^1.8.1", + "d3": "^7.8.0", + "dotenv": "^16.0.1", + "express": "^4.22.2", "formik": "^2.2.9", "framer-motion": "^4.1.17", - "graphql-tag": "^2.12.6", + "intro.js": "^7.2.0", + "intro.js-react": "^1.0.0", "isomorphic-unfetch": "^3.1.0", - "make-plural": "^6.2.2", + "json-2-csv": "^3.17.1", + "lodash-es": "^4.18.1", + "make-plural": "^7.1.0", "prop-types": "^15.8.1", - "react": "^16.14.0", - "react-dom": "^16.14.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", - "react-phone-input-2": "^2.14.0", - "react-router-dom": "^5.3.0", - "react-select": "^4.3.1", - "react-swipe": "^6.0.4", - "react-table": "^7.7.0", - "subscriptions-transport-ws": "^0.11.0", + "react-joyride": "^2.8.2", + "react-phone-input-2": "^2.15.1", + "react-router-dom": "^6.30.4", + "react-table": "^7.8.0", "yup": "^0.32.11" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.7", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-transform-runtime": "^7.16.8", - "@babel/preset-env": "^7.16.8", - "@babel/preset-react": "^7.16.7", - "@graphql-tools/mock": "^8.5.1", - "@graphql-tools/schema": "^7.1.5", - "@hot-loader/react-dom": "^16.14.0", - "@lingui/cli": "^3.13.0", - "@lingui/loader": "^3.13.0", - "@lingui/macro": "^3.13.0", - "@testing-library/jest-dom": "^5.16.1", - "@testing-library/react": "^11.2.7", + "@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": "^2.25.3", - "babel-loader": "^8.2.3", + "apollo-server": "^3.12.0", + "babel-loader": "^8.2.5", "babel-plugin-macros": "^3.1.0", "clean-webpack-plugin": "^3.0.0", + "copy-webpack-plugin": "^14.0.0", "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^24.7.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.3.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-standard": "^4.1.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": "^15.8.0", - "graphql-relay": "^0.9.0", + "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": "^10.0.32", - "jsonwebtoken": "^8.5.1", + "jest-emotion": "^11.0.0", + "jsonwebtoken": "^9.0.0", "mq-polyfill": "^1.1.8", - "nodemon": "^2.0.15", - "prettier": "^2.5.1", + "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.2", - "supertest": "^6.2.1", - "webpack": "^5.66.0", - "webpack-cli": "^4.9.1", + "react-test-renderer": "^18.2.0", + "source-map-loader": "^4.0.0", + "supertest": "^6.2.3", + "webpack": "^5.105.0", + "webpack-cli": "^5.1.4", "webpack-config-utils": "^2.3.1", - "webpack-dev-server": "^3.11.3" + "webpack-dev-server": "^5.2.4" }, "overrides": { - "colors": "1.4.0" + "colors": "1.4.0", + "form-data@^3.0.0": "3.0.4", + "form-data@^4.0.0": "4.0.4" }, "repository": { "type": "git", @@ -120,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..fcdcf002a8 100644 --- a/frontend/src/admin/AdminDomainCard.js +++ b/frontend/src/admin/AdminDomainCard.js @@ -1,37 +1,82 @@ 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, highAvailability, 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} + + + )} + {highAvailability && ( + + + HIGH AVAILABILITY + + + )} + {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, + highAvailability: bool, +} diff --git a/frontend/src/admin/AdminDomainList.js b/frontend/src/admin/AdminDomainList.js new file mode 100644 index 0000000000..11ed4e5d61 --- /dev/null +++ b/frontend/src/admin/AdminDomainList.js @@ -0,0 +1,104 @@ +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, string } from 'prop-types' + +export function AdminDomainList({ + nodes, + verified, + permission, + setSelectedRemoveProps, + removeOnOpen, + setModalProps, + updateOnOpen, +}) { + return ( + ( + + No Domains + + )} + > + {( + { + id: domainId, + domain, + claimTags, + archived, + rcode, + organizations, + assetState, + cvdEnrollment, + highAvailability, + }, + 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, + highAvailability, + permission, + }) + updateOnOpen() + }} + icon={} + aria-label={'Edit ' + domain} + mr="2" + /> + + + )} + + ) +} +AdminDomainList.propTypes = { + nodes: array.isRequired, + verified: bool, + permission: string, + 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..38394509d5 100644 --- a/frontend/src/admin/AdminDomainModal.js +++ b/frontend/src/admin/AdminDomainModal.js @@ -1,14 +1,13 @@ import React, { useRef } from 'react' -import { t, Trans } from '@lingui/macro' -import { useLingui } from '@lingui/react' +import { Trans, useLingui } from '@lingui/react/macro' import { + Badge, Box, Button, + Divider, + Flex, FormControl, - FormErrorMessage, - Grid, - IconButton, - Input, + FormLabel, Modal, ModalBody, ModalCloseButton, @@ -16,38 +15,56 @@ 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, + highAvailability, + permission, + ...rest }) { const toast = useToast() const initialFocusRef = useRef() - const { i18n } = useLingui() + const { t } = useLingui() const [createDomain] = useMutation(CREATE_DOMAIN, { - refetchQueries: ['PaginatedOrgDomains'], + refetchQueries: ['PaginatedOrgDomains', 'FindAuditLogs'], onError(error) { toast({ - title: i18n._(t`An error occurred.`), + title: t`An error occurred.`, description: error.message, status: 'error', duration: 9000, @@ -57,20 +74,18 @@ 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}`, - ), + title: t`Domain added`, + description: 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.`), + title: t`Unable to create new domain.`, description: createDomain.result.description, status: 'error', duration: 9000, @@ -79,8 +94,8 @@ export function AdminDomainModal({ }) } else { toast({ - title: i18n._(t`Incorrect send method received.`), - description: i18n._(t`Incorrect createDomain.result typename.`), + title: t`Incorrect send method received.`, + description: t`Incorrect createDomain.result typename.`, status: 'error', duration: 9000, isClosable: true, @@ -92,11 +107,10 @@ export function AdminDomainModal({ }) const [updateDomain] = useMutation(UPDATE_DOMAIN, { - refetchQueries: ['PaginatedOrgDomains'], - + refetchQueries: ['FindAuditLogs'], onError(error) { toast({ - title: i18n._(t`An error occurred.`), + title: t`An error occurred.`, description: error.message, status: 'error', duration: 9000, @@ -106,20 +120,18 @@ 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}`, - ), + title: t`Domain updated`, + description: 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.`), + title: t`Unable to update domain.`, description: updateDomain.result.description, status: 'error', duration: 9000, @@ -128,8 +140,8 @@ export function AdminDomainModal({ }) } else { toast({ - title: i18n._(t`Incorrect send method received.`), - description: i18n._(t`Incorrect updateDomain.result typename.`), + title: t`Incorrect send method received.`, + description: t`Incorrect updateDomain.result typename.`, status: 'error', duration: 9000, isClosable: true, @@ -138,21 +150,70 @@ 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), + highAvailability: values.highAvailability, }, }) } 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 +364,90 @@ export function AdminDomainModal({ ) } +const ArchiveDomainSwitch = withSuperAdmin(({ defaultChecked, handleChange, orgCount }) => { + const { t } = useLingui() + + return ( + + + + + + + + Archive domain + + + + + {orgCount > 0 ? ( + Note: This will affect results for {orgCount} organizations + ) : ( + Note: This could affect results for multiple organizations + )} + + + ) +}) + +const HighAvailabilitySwitch = withSuperAdmin(({ defaultChecked, handleChange }) => { + const { t } = useLingui() + + return ( + + + + + + + + High Availability + + + + ) +}) + +const IgnoreRuaToggle = withSuperAdmin(({ defaultChecked, handleChange }) => { + return ( + + + + + Ignore RUA + + + + ) +}) + AdminDomainModal.propTypes = { isOpen: bool, onClose: func, @@ -295,7 +455,16 @@ 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, + highAvailability: bool, + availableTags: array, + permission: string, } diff --git a/frontend/src/admin/AdminDomains.js b/frontend/src/admin/AdminDomains.js index 6fe425cc9d..512f0f7636 100644 --- a/frontend/src/admin/AdminDomains.js +++ b/frontend/src/admin/AdminDomains.js @@ -1,11 +1,12 @@ -import React, { useCallback, useState } from 'react' -import { t, Trans } from '@lingui/macro' +import React, { useCallback, useEffect, useState } from 'react' +import { Trans, useLingui } from '@lingui/react/macro' import { Box, Button, Divider, Flex, - IconButton, + FormControl, + FormLabel, Input, InputGroup, InputLeftElement, @@ -16,52 +17,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 { t } = 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' }, + highAvailability: false, + }) + 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 +98,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 +117,470 @@ 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: 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: t`Domain removed`, + description: 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: 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: t`Incorrect send method received.`, + description: 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} + permission={permission} + {...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 adceda0149..80ceb91b24 100644 --- a/frontend/src/admin/AdminPage.js +++ b/frontend/src/admin/AdminPage.js @@ -1,10 +1,9 @@ -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 { Trans, useLingui } from '@lingui/react/macro' import { useQuery } from '@apollo/client' -import { Link as RouteLink } from 'react-router-dom' -import { useLingui } from '@lingui/react' +import { Link as RouteLink, useNavigate, useParams } from 'react-router-dom' import { AdminPanel } from './AdminPanel' import { OrganizationInformation } from './OrganizationInformation' @@ -13,15 +12,24 @@ import { ADMIN_PAGE } from '../graphql/queries' import { Dropdown } from '../components/Dropdown' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' -import { bool } from 'prop-types' +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({ isLoginRequired }) { +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 { i18n } = useLingui() + const navigate = useNavigate() + const { t } = useLingui() const memoizedSetDebouncedSearchTermCallback = useCallback(() => { setDebouncedSearchTerm(searchTerm) @@ -52,6 +60,22 @@ export default function AdminPage({ isLoginRequired }) { }, }) + 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 } @@ -62,10 +86,10 @@ export default function AdminPage({ isLoginRequired }) { if (loading) { dropdown = ( setSearchTerm(val)} searchValue={searchTerm} /> @@ -73,34 +97,65 @@ export default function AdminPage({ isLoginRequired }) { } 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 = ( setSearchTerm(val)} searchValue={searchTerm} onChange={(opt) => { setOrgDetails(opt.value) setSelectedOrg(opt.label) }} + mr="auto" /> ) } - 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 50f488994a..5ccf183c28 100644 --- a/frontend/src/admin/OrganizationInformation.js +++ b/frontend/src/admin/OrganizationInformation.js @@ -22,44 +22,21 @@ import { import { CheckCircleIcon, MinusIcon, EditIcon } from '@chakra-ui/icons' import { bool, func, string } from 'prop-types' import { useMutation, useQuery } from '@apollo/client' -import { t, Trans } from '@lingui/macro' -import { useLingui } from '@lingui/react' +import { t } from "@lingui/core/macro" +import { Trans } from "@lingui/react/macro" import { Formik } from 'formik' import { ORGANIZATION_INFORMATION } from '../graphql/queries' -import { - REMOVE_ORGANIZATION, - UPDATE_ORGANIZATION, - LEAVE_ORG, -} from '../graphql/mutations' +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' -import { useHistory } from 'react-router-dom' - -export function OrganizationInformation({ - orgSlug, - removeOrgCallback: setSelectedOrg, - isLoginRequired, - ...props -}) { +import { getRequirement, schemaToValidation } from '../utilities/fieldRequirements' +import withSuperAdmin from '../app/withSuperAdmin' + +export function OrganizationInformation({ orgSlug, removeOrgCallback: setSelectedOrg, ...props }) { const toast = useToast() - const { i18n } = useLingui() - const history = useHistory() - const { - isOpen: isRemovalOpen, - onOpen: onRemovalOpen, - onClose: onRemovalClose, - } = useDisclosure() - const { - isOpen: leaveOrgIsOpen, - onOpen: leaveOrgOnOpen, - onClose: leaveOrgOnClose, - } = useDisclosure() + const { isOpen: isRemovalOpen, onOpen: onRemovalOpen, onClose: onRemovalClose } = useDisclosure() const removeOrgBtnRef = useRef() const [isEditingOrg, setIsEditingOrg] = useState(false) @@ -80,169 +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 + + 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', + }) - const removedOrgId = cache.identify( - data.removeOrganization.result.organization, - ) + const removedOrgId = cache.identify(data.removeOrganization.result.organization) - // Set admin page org to none, as the current is removed + setSelectedOrg('none') - cache.evict({ id: removedOrgId }) - }, + 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`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`An error occurred while updating this organization.`, - description: message, + 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.') - } - }, - }, - ) - - const [leaveOrganization, { loading: loadingLeaveOrg }] = useMutation( - LEAVE_ORG, - { - onError(error) { + } else { toast({ - title: i18n._(t`An error occurred.`), - description: error.message, + title: t`Incorrect typename received.`, + description: t`Incorrect updateOrganization.result typename.`, status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) - }, - onCompleted({ leaveOrganization }) { - if (leaveOrganization.result.__typename === 'LeaveOrganizationResult') { - toast({ - title: i18n._(t`Organization left successfully`), - description: i18n._(t`You have successfully left ${orgSlug}`), - status: 'success', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - leaveOrgOnClose() - history.go(0) - } else if (leaveOrganization.result.__typename === 'AffiliationError') { - toast({ - title: i18n._(t`Unable to leave organization.`), - description: leaveOrganization.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else { - toast({ - title: i18n._(t`Incorrect send method received.`), - description: i18n._( - t`Incorrect leaveOrganization.result typename.`, - ), - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - console.log('Incorrect leaveOrganization.result typename.') - } - }, + console.log('Incorrect updateOrganization.result typename.') + } }, - ) + }) if (loading) { return ( @@ -264,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 ( @@ -284,10 +194,7 @@ export function OrganizationInformation({ fontSize="3xl" > - {org.name}{' '} - {org.verified && ( - - )} + {org.name} {org.verified && }
@@ -312,19 +219,6 @@ export function OrganizationInformation({ aria-label={t`Edit Organization`} icon={} /> - {!isLoginRequired && ( - - )} @@ -346,6 +240,7 @@ export function OrganizationInformation({ provinceFR: '', cityEN: '', cityFR: '', + externalId: '', }} validationSchema={updateOrgValidationSchema} onSubmit={async (values, formikHelpers) => { @@ -382,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() } @@ -400,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. - - - - - - + @@ -438,12 +317,10 @@ export function OrganizationInformation({ - + + @@ -603,53 +451,33 @@ export function OrganizationInformation({ )}
- - - - - - Leave Organization - - - - - Are you sure you wish to leave {org.name}? You will have to be - invited back in to access it. - - - - - - - - - - ) } +const AcronymFields = withSuperAdmin(() => { + return ( + <> + + + + + + + + ) +}) + +const ExternalIdField = withSuperAdmin(() => { + return ( + + + + ) +}) + OrganizationInformation.propTypes = { orgSlug: string.isRequired, removeOrgCallback: func.isRequired, - isLoginRequired: bool, + 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 c9da9bf5d2..f3de2b82d4 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'], - }, - }, - 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 containing alphanumeric characters and periods, starting and ending with only alphanumeric characters/), - ).toBeInTheDocument(), - ) - - fireEvent.change(selectorInput, { - target: { value: 'selector1' }, }) 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,12 @@ describe('', () => { variables: { domainId: 'testid2=', orgId: 'testid=', - domain: 'test.domain.ca', - selectors: [], + tags: [], + archived: false, + assetState: 'MONITOR_ONLY', + ignoreRua: undefined, + cvdEnrollment: { status: 'NOT_ENROLLED' }, + highAvailability: false, }, }, result: { @@ -591,13 +511,14 @@ describe('', () => { }, ] - const { getByText, findByTestId, getByLabelText, queryByText } = render( + const { getByText, findByTestId, queryByText, findByRole } = render( @@ -607,6 +528,7 @@ describe('', () => { orgId={rawOrgDomainListData.findOrganizationBySlug.id} orgSlug={'test-org.slug'} domainsPerPage={4} + availableTags={rawOrgDomainListData.findOrganizationBySlug.availableTags} /> @@ -618,25 +540,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 8f306b5cc8..50ec096d92 100644 --- a/frontend/src/admin/__tests__/AdminPage.test.js +++ b/frontend/src/admin/__tests__/AdminPage.test.js @@ -1,16 +1,16 @@ 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_PAGE, ORGANIZATION_INFORMATION, @@ -18,27 +18,17 @@ import { 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( - + - - + + + + @@ -64,8 +54,10 @@ describe('', () => { > - - + + + + @@ -78,6 +70,9 @@ describe('', () => { }) userEvent.click(organizationInput) + await waitFor(() => { + expect(getByText(/Wolf Group/)).toBeInTheDocument() + }) const orgEntry = getByText(/Wolf Group/) userEvent.click(orgEntry) @@ -98,8 +93,10 @@ describe('', () => { > - - + + + + @@ -173,7 +170,8 @@ function mocks() { }, ], }, - isUserSuperAdmin: false, + isUserSuperAdmin: true, + isUserAdmin: true, }, }, }, @@ -205,7 +203,8 @@ function mocks() { }, ], }, - isUserSuperAdmin: false, + isUserSuperAdmin: true, + isUserAdmin: true, }, }, }, @@ -235,7 +234,13 @@ function mocks() { { 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: { @@ -283,7 +288,13 @@ function mocks() { { 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: { 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 0cd61859ca..701dc492f1 100644 --- a/frontend/src/admin/__tests__/OrganizationInformation.test.js +++ b/frontend/src/admin/__tests__/OrganizationInformation.test.js @@ -1,7 +1,7 @@ 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 { i18n } from '@lingui/core' @@ -14,10 +14,7 @@ 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' +import { REMOVE_ORGANIZATION, UPDATE_ORGANIZATION } from '../../graphql/mutations' i18n.loadLocaleData('en', { plurals: en }) i18n.load('en', { en: {} }) @@ -42,6 +39,7 @@ const mocks = [ province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -73,8 +71,8 @@ const mocks = [ query: UPDATE_ORGANIZATION, variables: { id: 'org-id', - acronymEN: 'NEWACREN', - acronymFR: 'NEWACRFR', + nameEN: 'NEW ACREN', + nameFR: 'NEW ACRFR', countryEN: 'Canada', }, }, @@ -83,7 +81,7 @@ const mocks = [ updateOrganization: { result: { id: 'org-id', - acronym: 'NEWACREN', + acronym: 'NEW ACREN', name: 'Org Name', slug: 'org-name', zone: 'org zone', @@ -92,6 +90,7 @@ const mocks = [ province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -116,10 +115,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -129,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 () => { @@ -145,10 +141,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -158,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: { @@ -183,6 +174,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -223,10 +215,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -235,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/, @@ -249,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 () => { @@ -284,6 +259,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -324,10 +300,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -353,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()) }) @@ -390,6 +357,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -418,8 +386,8 @@ describe('', () => { query: UPDATE_ORGANIZATION, variables: { id: 'org-id', - acronymEN: 'NEWACREN', - acronymFR: 'NEWACRFR', + nameEN: 'NEW ACREN', + nameFR: 'NEWA CRFR', countryEN: 'Canada', }, }, @@ -428,7 +396,7 @@ describe('', () => { updateOrganization: { result: { id: 'org-id', - acronym: 'NEWACREN', + acronym: 'NEW ACREN', name: 'Org Name', slug: 'org-name', zone: 'org zone', @@ -437,6 +405,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -457,10 +426,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -486,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()) }) @@ -516,10 +476,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -544,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) @@ -570,10 +523,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -589,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)', @@ -608,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()) @@ -645,6 +591,7 @@ describe('', () => { province: 'org province', city: 'org city', verified: true, + externalId: 'ORG123', __typename: 'Organization', }, }, @@ -676,8 +623,8 @@ describe('', () => { query: UPDATE_ORGANIZATION, variables: { id: 'org-id', - acronymEN: 'NEWACREN', - acronymFR: 'NEWACRFR', + nameEN: 'NEW ACREN', + nameFR: 'NEW ACRFR', countryEN: 'Canada', }, }, @@ -707,10 +654,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -726,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)', @@ -745,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()) @@ -776,10 +714,7 @@ describe('', () => { - {}} - /> + {}} /> @@ -795,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', @@ -807,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 38c749eee0..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,39 +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/) - 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: { - orgId: orgId, - requestedRole: 'USER', - userName: editingUserName, - preferredLang: 'ENGLISH', - }, - }, - result: { - data: { - inviteUserToOrg: { - result: { - status: 'User successfully invited', - __typename: 'InviteUserToOrgResult', + // 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() + }) + }) + 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, + }, + }, + result: { + data: { + inviteUserToOrg: { + result: { + description: 'User successfully invited', + __typename: 'AffiliationError', }, __typename: 'InviteUserToOrgPayload', }, @@ -456,11 +891,7 @@ describe('', () => { - + @@ -484,13 +915,160 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, + 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( + + + + + + + + + + + , ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, + + // 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(/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/ }) + + 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, }) @@ -498,7 +1076,7 @@ describe('', () => { // check for "success" toast and modal to close await waitFor(() => { - expect(getAllByText(/Email invitation sent/)[0]).toBeVisible() + expect(getAllByText(/Email invitation sent/)[0]).toBeInTheDocument() }) // wait for modal to close @@ -519,7 +1097,6 @@ describe('', () => { orgId: orgId, requestedRole: 'ADMIN', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { @@ -548,11 +1125,7 @@ describe('', () => { - + @@ -573,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', { @@ -588,16 +1157,8 @@ describe('', () => { // check for "success" toast await waitFor(() => { - expect(getAllByText(/Email invitation sent/)[0]).toBeVisible() + expect(getAllByText(/Email invitation sent/)[0]).toBeInTheDocument() }) - - // wait for modal to close - await waitFor( - () => { - expect(newUserRoleSelect).not.toBeInTheDocument() - }, - { timeout: 2000 }, - ) }) }) }) 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 2ffb9935e7..b0cf9ebc19 100644 --- a/frontend/src/app/App.js +++ b/frontend/src/app/App.js @@ -1,9 +1,8 @@ -import React, { Suspense, useEffect } from 'react' -import { Switch, Link as RouteLink, Redirect } from 'react-router-dom' -import { CSSReset, Flex, Link, Text } from '@chakra-ui/react' -import { t, Trans } from '@lingui/macro' -import { ErrorBoundary } from 'react-error-boundary' -import { useQuery } from '@apollo/client' +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' @@ -11,124 +10,84 @@ 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 { 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 { IS_LOGIN_REQUIRED } from '../graphql/queries' -// import { SlideMessage } from './SlideMessage' 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'), -) +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() { +export function App({ initialLoading, isLoginRequired }) { // Hooks to be used with this functional component - const { currentUser, isLoggedIn, isEmailValidated, currentTFAMethod } = - useUserVar() + const { currentUser, isLoggedIn, isEmailValidated, currentTFAMethod, hasAffiliation } = useUserVar() const { i18n } = useLingui() - const { data } = useQuery(IS_LOGIN_REQUIRED, {}) - - // 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() - } - }, [currentUser.jwt]) - return ( - - -
- - - Skip to main content - - -
- - - Home - - - {((isLoggedIn() && isEmailValidated()) || !data?.loginRequired) && ( - <> - - Organizations - - - Domains - - - DMARC Summaries - - - )} - - {isLoggedIn() && ( - - Account Settings - - )} - - {isLoggedIn() && isEmailValidated() && currentTFAMethod() !== 'NONE' && ( - - Admin Profile - - )} - - - {isLoggedIn() && !isEmailValidated() && ( - + 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,{' '} + To enable full app functionality and maximize your account's security,{' '} please verify your account @@ -136,242 +95,352 @@ export function App() { - )} + ) + } + } + } - {isLoggedIn() && isEmailValidated() && currentTFAMethod() === 'NONE' && ( - - - - To maximize your account's security,{' '} - - please activate a multi-factor authentication option - - . - - - + return ( + + +
+ + + Skip to main content + + +
+ + + Home + + + {initialLoading ? ( + <> + + LoadingState + + + LoadingState + + + ) : ( + <> + {((isLoggedIn() && isEmailValidated()) || !isLoginRequired) && ( + <> + + Organizations + + + Domains + + + )} + + {isLoggedIn() && isEmailValidated() && currentTFAMethod() !== 'NONE' && ( + + DMARC Summaries + + )} + + {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={ + - - )} - - - + } + /> + + - {() => ( - - - - )} - - - - {isLoggedIn() && - isEmailValidated() && - currentTFAMethod() !== 'NONE' ? ( - - ) : ( - - )} - - - + + + } + /> + + + + + } + /> + + - {() => ( - + element={ + - - )} - - - + } + /> + + - {() => ( - - - - )} - - - + + + } + /> + + - {() => ( - + element={ + - - )} - + + } + /> - - {() => ( - + element={ + - - )} - - - - {isLoggedIn() ? ( - - ) : ( - - )} - - - - {() => } - - - - {isLoggedIn() && - isEmailValidated() && - currentTFAMethod() !== 'NONE' ? ( - - ) : ( - - )} - - - - + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + -
- - -
- - Privacy - - - - Terms & conditions - - - - Report an Issue - - - - Contact Us - -
- + )} +
+ + +
+ + 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 index 4322e3d6cf..7ac31800b9 100644 --- a/frontend/src/app/ContactUsPage.js +++ b/frontend/src/app/ContactUsPage.js @@ -1,6 +1,7 @@ import React from 'react' -import { Trans } from '@lingui/macro' +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 ( @@ -9,35 +10,63 @@ export default function ContactUsPage() { Contact the Tracker Team - - - Government of Canada Employees - - - - Individuals from a departmental information technology group may - contact the TBS Cyber Security mailbox for ITPIN interpretation and - domain management. - - - - - Note that compliance data does not automatically refresh. - Modifications to domains could take 24 hours to update. - - - + + + + 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 8fd970c3b9..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, diff --git a/frontend/src/app/LocaleSwitcher.js b/frontend/src/app/LocaleSwitcher.js index d8aec5142b..b0156102be 100644 --- a/frontend/src/app/LocaleSwitcher.js +++ b/frontend/src/app/LocaleSwitcher.js @@ -30,7 +30,7 @@ const Toggler = (props) => { borderWidth="1px" borderColor="gray.300" aria-hidden - d="flex" + display="flex" color="primary" textTransform="uppercase" fontWeight="bold" @@ -47,10 +47,10 @@ const Toggler = (props) => { ) } -export function LocaleSwitcher() { +export function LocaleSwitcher({ ...props }) { const { i18n } = useLingui() return ( - + {i18n.locale === 'en' && } {i18n.locale === 'fr' && } diff --git a/frontend/src/app/NotificationBanner.js b/frontend/src/app/NotificationBanner.js index 66ed65e383..c1d352009c 100644 --- a/frontend/src/app/NotificationBanner.js +++ b/frontend/src/app/NotificationBanner.js @@ -1,24 +1,145 @@ -import React from 'react' -import { Flex, Box } from '@chakra-ui/react' -import { any } from 'prop-types' +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 + } -export function NotificationBanner({ children, ...props }) { return ( - - - {children} - + + + + + + {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/PrivatePage.js b/frontend/src/app/PrivatePage.js index dea1d9ce98..6c375efade 100644 --- a/frontend/src/app/PrivatePage.js +++ b/frontend/src/app/PrivatePage.js @@ -1,38 +1,19 @@ import React from 'react' import { bool, func } from 'prop-types' -import { Redirect, useLocation } from 'react-router-dom' - +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, isLoginRequired, ...rest }) { - const { isLoggedIn, isEmailValidated } = useUserVar() +export function PrivatePage({ children, condition, ...rest }) { const location = useLocation() - return ( - - (isLoggedIn() && isEmailValidated()) || !isLoginRequired ? ( - 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 index 77fc6cc0da..36de7aa7f5 100644 --- a/frontend/src/app/SlideMessage.js +++ b/frontend/src/app/SlideMessage.js @@ -1,7 +1,7 @@ import React from 'react' import { useLingui } from '@lingui/react' -import { Trans } from '@lingui/macro' +import { Trans } from "@lingui/react/macro" import sigEn from '../images/goc-header-logo-en.svg' import sigFr from '../images/goc-header-logo-fr.svg' diff --git a/frontend/src/app/TopBanner.js b/frontend/src/app/TopBanner.js index 133967fc1f..2df0ea448b 100644 --- a/frontend/src/app/TopBanner.js +++ b/frontend/src/app/TopBanner.js @@ -1,13 +1,14 @@ import React from 'react' -import { t, Trans } from '@lingui/macro' -import { Box, Button, Flex, useToast, Image } 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/Asset6.svg' -import trackerText from '../images/Asset3.svg' +import trackerLogo from '../images/tracker_v-03-transparent 2.svg' +import trackerText from '../images/trackerlogo 1.svg' import { LocaleSwitcher } from './LocaleSwitcher' @@ -16,8 +17,11 @@ import { useUserVar } from '../utilities/userState' 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) => { +export const TopBanner = ({ initialLoading, ...props }) => { const { isLoggedIn, logout } = useUserVar() const toast = useToast() const { i18n } = useLingui() @@ -26,7 +30,7 @@ export const TopBanner = (props) => { 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, @@ -48,85 +52,111 @@ export const TopBanner = (props) => { return ( - - - {t`Symbol - - - {t`Tracker - - - {t`Tracker - + + + + + {t`Symbol + + + + + + + + {t`Tracker + + + {t`Tracker + + + BETA} - ml={{ base: '0', md: 'auto' }} + phase={ + + + BETA + + + PREVIEW + + + } + ml="8" mr="2" + flexShrink="0" > This is a new service, we are constantly improving. - - - - - {isLoggedIn() ? ( - - ) : ( - <> - - - - )} + + + + {initialLoading ? ( + <> + + + + + + + + ) : isLoggedIn() ? ( + <> + + + ) : ( + <> + + {window.env?.APP_IS_PRODUCTION === true && ( + + )} + + )} + ) } + +TopBanner.propTypes = { + initialLoading: bool, +} diff --git a/frontend/src/app/__tests__/App.test.js b/frontend/src/app/__tests__/App.test.js index 063c1df6c8..def4e881d6 100644 --- a/frontend/src/app/__tests__/App.test.js +++ b/frontend/src/app/__tests__/App.test.js @@ -4,25 +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' import { IS_LOGIN_REQUIRED } from '../../graphql/queries' - -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) +import { TourProvider } from '../../userOnboarding/contexts/TourContext' const mocks = [ { @@ -72,7 +60,9 @@ describe('', () => { - + + + @@ -97,7 +87,9 @@ describe('', () => { - + + + @@ -126,7 +118,9 @@ describe('', () => { - + + + @@ -135,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( @@ -182,7 +174,9 @@ describe('', () => { - + + + @@ -190,15 +184,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('When login is not required', () => { - it('displays additional navbar options', () => { + it('displays additional navbar options', async () => { const { queryByRole } = render( ', () => { - + + + , ) - expect( - queryByRole('link', { name: 'Organizations', hidden: false }), - ).toBeInTheDocument() + 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 index 01bda95544..76ff46c917 100644 --- a/frontend/src/app/__tests__/ContactUsPage.test.js +++ b/frontend/src/app/__tests__/ContactUsPage.test.js @@ -4,24 +4,11 @@ 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 { setupI18n } from '@lingui/core' +import { i18n } from '@lingui/core' import { makeVar } from '@apollo/client' -import { en } from 'make-plural/plurals' - import ContactUsPage from '../ContactUsPage' - import { UserVarProvider } from '../../utilities/userState' -const i18n = setupI18n({ - locale: 'en', - messages: { - en: {}, - }, - localeData: { - en: { plurals: en }, - }, -}) - describe('', () => { afterEach(cleanup) @@ -68,9 +55,7 @@ describe('', () => {
, ) - const button = await waitFor(() => - getByRole('link', { name: /Contact Us/i }), - ) + 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('