diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000000..b92cb42751c --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,24 @@ +# Use Debian Bookworm as the base image +FROM ubuntu:latest as builder + +# Update package list and install dependencies +RUN apt-get update && \ + apt-get install -y \ + git \ + vim \ + curl \ + gnupg2 \ + lsb-release \ + ca-certificates + +# Install Node.js (latest LTS version) and npm +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt-get install -y nodejs + +# Install additional developer tools (optional) +RUN apt-get install -y \ + neovim \ + gh # GitHub CLI + +# Default command (to keep the container running) +CMD ["tail", "-f", "/dev/null"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..89af2b369c5 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,36 @@ +{ + "name": "talawa api dev environment", + "dockerComposeFile": "docker-compose.yaml", + "service": "devcontainer", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + // Settings to apply to the workspace. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "typescript.tsdk": "node_modules/typescript/lib", + "database.host": "mongodb", + "redis.host": "redis-stack-server" + }, + // List of extensions to install inside the container + "extensions": [ + "dbaeumer.vscode-eslint", + "ms-azuretools.vscode-docker", + "esbenp.prettier-vscode", + "redhat.vscode-yaml" + ] + } + }, + // Set up forward ports + "forwardPorts": [ + 4000, // Server port + 27017, // MongoDB port + 6379 // Redis port + ], + // Post-create commands to run after creating the container + "postCreateCommand": "npm install", + + // Volumes from docker-compose are already included + "shutdownAction": "stopCompose" +} diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml new file mode 100644 index 00000000000..253b59f9663 --- /dev/null +++ b/.devcontainer/docker-compose.yaml @@ -0,0 +1,34 @@ +services: + mongodb: + image: mongo:latest + ports: + - 27017:27017 + volumes: + - mongodb-data:/data/db + + redis-stack-server: + image: redis/redis-stack-server:latest + ports: + - 6379:6379 + volumes: + - redis-data:/data + + devcontainer: + build: + context: . + dockerfile: Dockerfile + ports: + - "${SERVER_PORT:-4000}:4000" + volumes: + - ../..:/workspaces:cached + depends_on: + - mongodb + - redis-stack-server + environment: + - MONGO_DB_URL=mongodb://mongodb:27017 + - REDIS_HOST=redis-stack-server + - REDIS_PORT=6379 + +volumes: + mongodb-data: + redis-data: diff --git a/.dockerignore b/.dockerignore index 06d9a3ecb26..d0adaf26716 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ node_modules videos images +data .env .git .gitignore diff --git a/.env.sample b/.env.sample index 448f36a01ad..fa6a0fabaa4 100644 --- a/.env.sample +++ b/.env.sample @@ -84,6 +84,24 @@ REDIS_HOST= REDIS_PORT= REDIS_PASSWORD= +# These environment variables are used to provide MinIo credentials + +# The endpoint URL for MinIO server, specifying where the MinIO service is hosted +MINIO_ENDPOINT= + +# The username with root-level access for MinIO administration +MINIO_ROOT_USER= + +# The password corresponding to the MINIO_ROOT_USER for authentication +MINIO_ROOT_PASSWORD= + +# The default bucket name to use with MinIO for storing data +MINIO_BUCKET= + +# The local directory where MinIO stores its data files +MINIO_DATA_DIR= + + # this environment variable is for setting the environment variable for Image Upload size IMAGE_SIZE_LIMIT_KB=3000 \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1aa9d8a2d3a..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -.github -.vscode -build -coverage -node_modules -src/types -docs/Schema.md \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 79ad3ed89a5..00000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "env": { - "es2022": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "overrides": [ - { - "files": ["*.ts"], // Specify that the following rules apply only to TypeScript files - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json", - "tsconfigRootDir": ".", - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": { - // Typescript additional rules - "@typescript-eslint/array-type": "error", - "@typescript-eslint/consistent-type-assertions": "error", - "@typescript-eslint/consistent-type-imports": "error", - "@typescript-eslint/explicit-function-return-type": "error", - // Interfaces must begin with Interface or TestInterface followed by a PascalCase name - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "interface", - "format": ["PascalCase"], - "prefix": ["Interface", "TestInterface"] - }, - { - "selector": ["typeAlias", "typeLike", "enum"], - "format": ["PascalCase"] - }, - { - "selector": "typeParameter", - "format": ["PascalCase"], - "prefix": ["T"] - }, - { - "selector": "variable", - "format": ["camelCase", "UPPER_CASE"], - "leadingUnderscore": "allow" - }, - { - "selector": "parameter", - "format": ["camelCase"], - "leadingUnderscore": "allow" - }, - { - "selector": "function", - "format": ["camelCase"] - }, - { - "selector": "memberLike", - "modifiers": ["private"], - "format": ["camelCase"], - "leadingUnderscore": "require" - }, - { - "selector": "variable", - "modifiers": ["exported"], - "format": null - } - ] - } - }, - { - "files": ["./src/typeDefs/**/*.ts"], - "processor": "@graphql-eslint/graphql" - }, - { - "files": ["./src/typeDefs/**/*.graphql"], - "parser": "@graphql-eslint/eslint-plugin", - "plugins": ["@graphql-eslint"] - }, - { - "files": ["tests/**/*"], - "rules": { - "no-restricted-imports": "off" - } - }, - { - // Disable explicit function return type for index.ts as it uses a lot of templated code - // which has convulated return types - "files": ["./src/index.ts", "./src/utilities/copyToClipboard.ts"], - "rules": { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-empty-function": "off" - } - } - ], - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint", "eslint-plugin-tsdoc", "import"], - "root": true, - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": ["**/src/**"] - } - ], - // restrict the use of same package in multiple import statements - "import/no-duplicates": "error", - // warn/1, error/2, off/0 - "tsdoc/syntax": "error", - // Typescript Rules - "@typescript-eslint/ban-ts-comment": "error", - "@typescript-eslint/no-unsafe-function-type": "error", - "@typescript-eslint/no-wrapper-object-types": "error", - "@typescript-eslint/no-empty-object-type": "error", - "@typescript-eslint/no-duplicate-enum-values": "error", - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-non-null-asserted-optional-chain": "error", - "@typescript-eslint/no-non-null-assertion": "error", - "@typescript-eslint/no-var-requires": "error" - } -} diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 07c49270a02..81d68df4bdb 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -7,12 +7,12 @@ updates: directory: "/" # Schedule automated updates to run weekly schedule: - interval: "weekly" + interval: "monthly" # Labels to apply to Dependabot PRs labels: - "dependencies" # Specify the target branch for PRs - target-branch: "develop" + target-branch: "develop-postgres" # Customize commit message prefix commit-message: prefix: "chore(deps):" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 34541cabbd9..37ae6b87e83 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -24,10 +24,6 @@ Thanks for submitting a pull request! Please provide enough information so that Fixes # -**Did you add tests for your changes?** - - - **Snapshots/Videos:** @@ -45,6 +41,19 @@ Fixes # +## Checklist + +### CodeRabbit AI Review +- [ ] I have reviewed and addressed all critical issues flagged by CodeRabbit AI +- [ ] I have implemented or provided justification for each non-critical suggestion +- [ ] I have documented my reasoning in the PR comments where CodeRabbit AI suggestions were not implemented + +### Test Coverage +- [ ] I have written tests for all new changes/features +- [ ] I have verified that test coverage meets or exceeds 95% +- [ ] I have run the test suite locally and all tests pass + + **Other information** diff --git a/.github/workflows/codeql-codescan.yml b/.github/workflows/codeql-codescan.yml index 272e67db8b3..134951d2ed0 100644 --- a/.github/workflows/codeql-codescan.yml +++ b/.github/workflows/codeql-codescan.yml @@ -21,7 +21,8 @@ on: jobs: CodeQL: - name: CodeQL + if: ${{ github.actor != 'dependabot[bot]' }} + name: Analyse Code With CodeQL runs-on: ubuntu-latest permissions: actions: read @@ -47,4 +48,4 @@ jobs: uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/.github/workflows/eslint_disable_check.py b/.github/workflows/eslint_disable_check.py new file mode 100644 index 00000000000..95702064ee4 --- /dev/null +++ b/.github/workflows/eslint_disable_check.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""ESLint Checker Script. + +Methodology: + + Recursively analyzes TypeScript files in the 'src' directory and its subdirectories + as well as 'setup.ts' files to ensure they do not contain eslint-disable statements. + + This script enforces code quality practices in the project. + +NOTE: + + This script complies with our python3 coding and documentation standards. + It complies with: + + 1) Pylint + 2) Pydocstyle + 3) Pycodestyle + 4) Flake8 + +""" + +import os +import re +import argparse +import sys + +def has_eslint_disable(file_path): + """ + Check if a TypeScript file contains eslint-disable statements. + + Args: + file_path (str): Path to the TypeScript file. + + Returns: + bool: True if eslint-disable statement is found, False otherwise. + """ + eslint_disable_pattern = re.compile(r'//\s*eslint-disable(?:-next-line|-line)?', re.IGNORECASE) + + try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + return bool(eslint_disable_pattern.search(content)) + except Exception as e: + print(f"Error reading file {file_path}: {e}") + return False + +def check_eslint(directory): + """ + Recursively check TypeScript files for eslint-disable statements in the 'src' directory. + + Args: + directory (str): Path to the directory. + + Returns: + list: List of files containing eslint-disable statements. + """ + eslint_issues = [] + + src_dir = os.path.join(directory, 'src') + + if not os.path.exists(src_dir): + print(f"Source directory '{src_dir}' does not exist.") + return eslint_issues + + for root, dirs, files in os.walk(src_dir): + for file_name in files: + if file_name.endswith('.tsx') and not file_name.endswith('.test.tsx'): + file_path = os.path.join(root, file_name) + if has_eslint_disable(file_path): + eslint_issues.append(f'File {file_path} contains eslint-disable statement.') + + setup_path = os.path.join(directory, 'setup.ts') + if os.path.exists(setup_path) and has_eslint_disable(setup_path): + eslint_issues.append(f'Setup file {setup_path} contains eslint-disable statement.') + + return eslint_issues + +def arg_parser_resolver(): + """Resolve the CLI arguments provided by the user.""" + parser = argparse.ArgumentParser() + parser.add_argument( + "--directory", + type=str, + default=os.getcwd(), + help="Path to the directory to check (default: current directory)" + ) + return parser.parse_args() + +def main(): + """ + Execute the script's main functionality. + + This function serves as the entry point for the script. It performs + the following tasks: + 1. Validates and retrieves the directory to check from + command line arguments. + 2. Recursively checks TypeScript files for eslint-disable statements. + 3. Provides informative messages based on the analysis. + 4. Exits with an error if eslint-disable statements are found. + + Raises: + SystemExit: If an error occurs during execution. + """ + args = arg_parser_resolver() + if not os.path.exists(args.directory): + print(f"Error: The specified directory '{args.directory}' does not exist.") + sys.exit(1) + + eslint_issues = check_eslint(args.directory) + + if eslint_issues: + for issue in eslint_issues: + print(issue) + print("ESLint-disable check failed. Exiting with error.") + sys.exit(1) + + print("ESLint-disable check completed successfully.") + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/.github/workflows/pull-request-target.yml b/.github/workflows/pull-request-target.yml index fde5f05808e..6fef4ed474f 100644 --- a/.github/workflows/pull-request-target.yml +++ b/.github/workflows/pull-request-target.yml @@ -25,17 +25,21 @@ jobs: message: | ## Our Pull Request Approval Process - We have these basic policies to make the approval process smoother for our volunteer team. + Thanks for contributing! ### Testing Your Code - - Please make sure your code passes all tests. Our test code coverage system will fail if these conditions occur: - - 1. The overall code coverage drops below the target threshold of the repository - 2. Any file in the pull request has code coverage levels below the repository threshold - 3. Merge conflicts - - The process helps maintain the overall reliability of the code base and is a prerequisite for getting your PR approved. Assigned reviewers regularly review the PR queue and tend to focus on PRs that are passing. + + Remember, your PRs won't be reviewed until these criteria are met: + + 1. We don't merge PRs with poor code quality. + 1. Follow coding best practices such that CodeRabbit.ai approves your PR. + 1. We don't merge PRs with failed tests. + 1. When tests fail, click on the `Details` link to learn more. + 1. Write sufficient tests for your changes (CodeCov Patch Test). Your testing level must be better than the target threshold of the repository + 1. Tests may fail if you edit sensitive files. Ask to add the `ignore-sensitive-files-pr` label if the edits are necessary. + 1. We cannot merge PRs with conflicting files. These must be fixed. + + Our policies make our code better. ### Reviewers diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 0db1546c20f..44b5939049e 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -37,7 +37,7 @@ jobs: - name: Count number of lines run: | chmod +x ./.github/workflows/countline.py - ./.github/workflows/countline.py --lines 600 --exclude_files src/types/generatedGraphQLTypes.ts tests src/typeDefs/types.ts src/constants.ts + ./.github/workflows/countline.py --lines 600 --exclude_files src/types/generatedGraphQLTypes.ts tests src/typeDefs/types.ts src/constants.ts src/typeDefs/inputs.ts - name: Check for TSDoc comments run: npm run check-tsdoc # Run the TSDoc check script @@ -74,7 +74,7 @@ jobs: if: steps.changed_files.outputs.any_changed == 'true' env: CHANGED_FILES: ${{ steps.changed_files.outputs.all_changed_files }} - run: npx eslint ${CHANGED_FILES} + run: npx eslint ${CHANGED_FILES} --max-warnings=1500 && python .github/workflows/eslint_disable_check.py - name: Check for formatting errors run: npm run format:check @@ -90,8 +90,9 @@ jobs: echo "Error: Source and Target Branches are the same. Please ensure they are different." exit 1 - Check-Unauthorized-Changes: - name: Checks if no unauthorized files are changed + Check-Sensitive-Files: + if: ${{ github.actor != 'dependabot[bot]' && !contains(github.event.pull_request.labels.*.name, 'ignore-sensitive-files-pr') }} + name: Checks if sensitive files have been changed without authorization runs-on: ubuntu-latest steps: - name: Checkout code @@ -126,6 +127,13 @@ jobs: setup.ts schema.graphql .coderabbit.yaml + CODE_OF_CONDUCT.md + CONTRIBUTING.md + DOCUMENTATION.md + INSTALLATION.md + ISSUE_GUIDELINES.md + PR_GUIDELINES.md + README.md - name: List all changed unauthorized files if: steps.changed-unauth-files.outputs.any_changed == 'true' || steps.changed-unauth-files.outputs.any_deleted == 'true' @@ -138,6 +146,7 @@ jobs: exit 1 File-count-check: + if: ${{ github.actor != 'dependabot[bot]' }} name: Checks if number of files changed is acceptable runs-on: ubuntu-latest steps: @@ -288,6 +297,7 @@ jobs: min_coverage: 95.0 JSDocs: + if: ${{ github.actor != 'dependabot[bot]' }} name: 'JSDocs comments and pipeline' runs-on: ubuntu-latest needs: Test-Application @@ -303,6 +313,7 @@ jobs: run: echo "Run JSdocs :${{ env.RUN_JSDOCS }}" Branch-check: + if: ${{ github.actor != 'dependabot[bot]' }} name: "Base branch check" runs-on: ubuntu-latest steps: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 90c6dfbcf72..d94f50a3d89 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -26,6 +26,7 @@ jobs: ############################################################################## Push-Workflow: + if: ${{ github.actor != 'dependabot[bot]' }} name: Testing Application runs-on: ubuntu-latest strategy: @@ -54,8 +55,6 @@ jobs: MONGO_DB_URL: mongodb://localhost:27017/talawa-test-db REDIS_HOST: localhost REDIS_PORT: 6379 -# ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN_SECRET }} -# REFRESH_TOKEN_SECRET: ${{ secrets.REFRESH_TOKEN_SECRET }} # We checkout the content of the Talawa-API repository in a directory called `api` steps: @@ -95,6 +94,7 @@ jobs: # You can find the deployment instructions in the scripts/cloud-api-demo/README.md file Deploy-Workflow: + if: ${{ github.actor != 'dependabot[bot]' }} name: Deploying Application to Cloud VPS needs: Push-Workflow runs-on: ubuntu-latest @@ -130,6 +130,7 @@ jobs: python3 /usr/local/bin/scripts/deploy.py --path ~/develop --branch develop Check-Schema: + if: ${{ github.actor != 'dependabot[bot]' }} name: Check Schema runs-on: ubuntu-latest diff --git a/.github/workflows/md_mdx_format_adjuster.py b/.github/workflows/talawa_api_md_mdx_format_adjuster.py similarity index 100% rename from .github/workflows/md_mdx_format_adjuster.py rename to .github/workflows/talawa_api_md_mdx_format_adjuster.py diff --git a/.gitignore b/.gitignore index e25629d39ec..8ce4e0b129a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,9 @@ pnpm-lock.yaml coverage build +# Ignore the folder for file uploads meta data in minio +data/** + serviceAccountKey.json cert.pem key.pem diff --git a/.husky/pre-commit b/.husky/pre-commit index 71b41a852c5..faf9de7244e 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,10 +1,8 @@ -#!/usr/bin/env sh # Disable the hooks in CI [ -n "$CI" ] && exit 0 # Change to the current directory -. "$(dirname -- "$0")/_/husky.sh" # Checks code for typescript type errors and throws errors if found. npm run typecheck diff --git a/.node-version b/.node-version index 751f4c9f38b..0cd71bae497 100644 Binary files a/.node-version and b/.node-version differ diff --git a/Dockerfile.dev b/Dockerfile.dev index 93e0564184c..3fda0f43c26 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -11,4 +11,4 @@ COPY . . EXPOSE 4000 -CMD ["npm", "run", "dev"] +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/INSTALLATION.md b/INSTALLATION.md index f57b8901239..d783349afc7 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -15,6 +15,7 @@ This document provides instructions on how to set up and start a running instanc - [Install node.js](#install-nodejs) - [Install TypeScript](#install-typescript) - [Install Required Packages](#install-required-packages) + - [Installation Using Devpod](#install-using-devpod) - [Installation Using Docker](#installation-using-docker) - [Run the Talawa-API Setup](#run-the-talawa-api-setup) - [Install the Docker Application](#install-the-docker-application) @@ -29,6 +30,15 @@ This document provides instructions on how to set up and start a running instanc - [Install Redis](#install-redis) - [Performance Benefits](#performance-benefits) - [Setting Up Redis](#setting-up-redis) + - [Set Up MinIO](#set-up-minio) + - [Install MinIO](#install-minio) + - [1. Using the Setup Script](#1-using-the-setup-script) + - [2. Manual MinIO Installation](#2-manual-minio-installation) + - [Running MinIO with Talawa-API](#running-minio-with-talawa-api) + - [1. Using Docker](#1-using-docker) + - [2. Running Locally](#2-running-locally) + - [Access MinIO](#access-minio) + - [Create a Bucket](#create-a-bucket) - [Configuration](#configuration) - [Automated Configuration of `.env`](#automated-configuration-of-env) - [Manual Configuration of `.env`](#manual-configuration-of-env) @@ -50,6 +60,7 @@ This document provides instructions on how to set up and start a running instanc - [Setting up the RECAPTCHA_SECRET_KEY](#setting-up-the-recaptcha_secret_key) - [Setting up .env MAIL_USERNAME and MAIL_PASSWORD ReCAPTCHA Parameters](#setting-up-env-mail_username-and-mail_password-recaptcha-parameters) - [Setting up SMTP Email Variables in the .env File](#setting-up-smtp-email-variables-in-the-env-file) + - [Setting up MinIO configurations](#setting-up-minio-configurations) - [Setting up Logger configurations](#setting-up-logger-configurations) - [Setting up COLORIZE_LOGS in .env file](#setting-up-colorize_logs-in-env-file) - [Setting up LOG_LEVEL in .env file](#setting-up-log_level-in-env-file) @@ -88,6 +99,9 @@ This document provides instructions on how to set up and start a running instanc - [Install Required Packages](#install-required-packages) - [Installation Using Docker](#installation-using-docker) - [Run the Talawa-API Setup](#run-the-talawa-api-setup) +- [Install Using Devpod](#install-using-devpod) + - [Setting Up Talawa-Api with Devpod (CLI Version)](#setting-up-talawa-api-with-devpod-cli-version) + - [Setting Up Talawa-Api with Devpod (GUI Version)](#setting-up-talawa-api-with-devpod-gui-version) - [Install the Docker Application](#install-the-docker-application) - [Docker Compose Setup](#docker-compose-setup) - [Prerequisites](#prerequisites-1) @@ -102,6 +116,16 @@ This document provides instructions on how to set up and start a running instanc - [Install Redis](#install-redis) - [Performance Benefits](#performance-benefits) - [Setting Up Redis](#setting-up-redis) + - [Set Up MinIO](#set-up-minio) + - [Install MinIO](#install-minio) + - [1. Using the Setup Script](#1-using-the-setup-script) + - [2. Manual MinIO Installation](#2-manual-minio-installation) + - [Running MinIO with Talawa-API](#running-minio-with-talawa-api) + - [1. Using Docker](#1-using-docker) + - [2. Running Locally](#2-running-locally) + - [Customize MinIO Data Directory](#customize-minio-data-directory) + - [Access MinIO](#access-minio) + - [Create a Bucket](#create-a-bucket) - [Configuration](#configuration) - [Automated Configuration of `.env`](#automated-configuration-of-env) - [Manual Configuration of `.env`](#manual-configuration-of-env) @@ -123,6 +147,7 @@ This document provides instructions on how to set up and start a running instanc - [Setting up the RECAPTCHA_SECRET_KEY](#setting-up-the-recaptcha_secret_key) - [Setting up .env MAIL_USERNAME and MAIL_PASSWORD ReCAPTCHA Parameters](#setting-up-env-mail_username-and-mail_password-recaptcha-parameters) - [Setting up SMTP Email Variables in the .env File](#setting-up-smtp-email-variables-in-the-env-file) + - [Setting up MinIO configurations](#setting-up-minio-configurations) - [Setting up Logger configurations](#setting-up-logger-configurations) - [Setting up COLORIZE_LOGS in .env file](#setting-up-colorize_logs-in-env-file) - [Setting up LOG_LEVEL in .env file](#setting-up-log_level-in-env-file) @@ -288,6 +313,72 @@ We have created a setup script to make configuring Talawa-API easier. ``` npm run setup ``` +# Install Using Devpod + +This guide provides a step-by-step guide to setting up a Talawa-Api server using Devpod. + +## Setting Up Talawa-Api with Devpod (CLI Version) + +1. **Install Devpod CLI**: + - Download and install the Devpod CLI from [Devpod CLI Installation Guide](https://devpod.sh/docs/getting-started/install#optional-install-devpod-cli). + +2. **Add a Provider**: + - Use Docker or a compatible provider like Podman or Colima. + - Install Docker from their [official documentation](https://docs.docker.com/engine/install/). + - Add a provider using the CLI by following [this guide](https://devpod.sh/docs/getting-started/quickstart-devpod-cli#add-a-provider). + +3. **Create a Workspace**: + - Run the following command in your terminal to start the workspace: + ```bash + devpod up https://github.com/PalisadoesFoundation/talawa-api@develop + ``` + - For more information on creating a workspace, refer to the [Devpod CLI workspace guide](https://devpod.sh/docs/developing-in-workspaces/create-a-workspace#git-repository). + +4. **Select Your IDE**: + - To choose your ide refer to [Devpod CLI ide guide](https://devpod.sh/docs/developing-in-workspaces/connect-to-a-workspace#vs-code) + +5. **Set Up Talawa-Api**: + - Once your IDE is open and the workspace is ready, set up the environment by running: + ```bash + npm run setup + ``` + - Start the Talawa API server by running: + ```bash + npm run dev + ``` + +## Setting Up Talawa-Api with Devpod (GUI Version) + +1. **Install Devpod GUI Application**: + - Download and install the Devpod GUI from [Devpod GUI Installation Guide](https://devpod.sh/docs/getting-started/install). + +2. **Add a Provider**: + - Use Docker or a compatible provider like Podman or Colima. + - Install Docker from their [official documentation](https://docs.docker.com/engine/install/). + - Add a provider using the GUI app by following [this guide](https://devpod.sh/docs/getting-started/quickstart-vscode#add-a-provider). + +3. **Create a Workspace**: + - Open the Devpod GUI application. + - Start a new workspace by entering the following URL in the GUI: + ``` + https://github.com/PalisadoesFoundation/talawa-api@develop + ``` + - For more information on starting a workspace in the GUI, refer to [this guide](https://devpod.sh/docs/getting-started/quickstart-vscode#start-a-workspace-with-vs-code). + +4. **Select Your IDE**: + - In the Devpod GUI, select your desired IDE from the available options. + +5. **Set Up Talawa-Api**: + - Once your IDE is open and the workspace is ready, set up the environment by running: + ```bash + npm run setup + ``` + - Start the Talawa API server by running: + ```bash + npm run dev + ``` + + ## Install the Docker Application @@ -317,16 +408,14 @@ Follow these steps for setting up a software development environment. docker-compose -f docker-compose.dev.yaml up --build ``` - 2. Using Ubuntu: - 1. Running synchronously. Using CTRL-C will stop it. - ```bash + 2. Using Ubuntu: 1. Running synchronously. Using CTRL-C will stop it. + `bash sudo /usr/libexec/docker/cli-plugins/docker-compose -f docker-compose.dev.yaml up --build - ``` - 2. Running asynchronously in a subshell. You will have to use the `docker-compose down` command below to stop it. - `bash - sudo /usr/libexec/docker/cli-plugins/docker-compose -f docker-compose.dev.yaml up --build & - ` - This command starts the development environment, where you can make changes to the code, and the server will automatically restart. + ` 2. Running asynchronously in a subshell. You will have to use the `docker-compose down` command below to stop it. + `bash +sudo /usr/libexec/docker/cli-plugins/docker-compose -f docker-compose.dev.yaml up --build & +` + This command starts the development environment, where you can make changes to the code, and the server will automatically restart. 2. Accessing the Development Application: Open your web browser and navigate to: @@ -489,6 +578,110 @@ Talawa-api makes use of `Redis` for caching frequently accessed data items in th Remember to adjust any paths or details as needed for your specific environment. After following these steps, you will have successfully set up Redis. +## Set Up MinIO + +Talawa-API uses MinIO, an open-source object storage system, for storing uploaded files. MinIO will be hosted alongside Talawa-API itself, providing a self-contained solution for file storage. + +### Install MinIO + +You can either use the setup script for automatic configuration or install MinIO manually. + +#### 1. Using the Setup Script + +1. Run the setup script. +2. The script will automatically set up environment variables, including MinIO credentials. + + Details of what each prompt means can be found in the [Configuration](#configuration) section of this document. + + ```bash + npm run setup + ``` + +#### 2. Manual MinIO Installation + +If you choose to set up MinIO manually, follow the instructions for your operating system below. Note that the MinIO server should not be started manually as it will be handled by the Talawa-API. + +1. For Linux Users + + - Download and Install MinIO + ```bash + wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20240817012454.0.0_amd64.deb -O minio.deb + sudo dpkg -i minio.deb + ``` + - Follow the official installation guide: [MinIO Installation Guide for Linux](https://min.io/docs/minio/linux/index.html) + +2. For Windows Users + + - Download and Install MinIO + ``` + https://dl.min.io/server/minio/release/windows-amd64/minio.exe + ``` + - Follow the official installation guide: [MinIO Installation Guide for Windows](https://min.io/docs/minio/windows/index.html) + +3. For macOS Users + - Download and Install MinIO + ```bash + brew install minio/stable/minio + ``` + - Follow the official installation guide: [MinIO Installation Guide for macOS](https://min.io/docs/minio/macos/index.html) + +### Running MinIO with Talawa-API + +You can run MinIO alongside Talawa-API using Docker or locally. Both methods ensure that MinIO is set up and running with Talawa-API. + +#### 1. Using Docker + +To run MinIO along with Talawa-API using Docker, use the following command: + +```bash +docker compose -f up +``` + +Replace `` with the name of the Docker Compose file. This command will start both the Talawa-API and MinIO services as defined in the Docker Compose file. + +#### 2. Running Locally + +If you prefer to run MinIO and Talawa-API locally, use the provided npm scripts. These scripts will ensure that MinIO is installed (if not already) and start the Talawa-API server along with MinIO. + +- To run the development server with MinIO: + + ```bash + npm run dev:with-minio + ``` + +- To start the production server with MinIO: + + ```bash + npm run start:with-minio + ``` + +These npm scripts will check if MinIO is installed on your system. If MinIO is not installed, the scripts will install it automatically before starting the Talawa-API server. + +### Customize MinIO Data Directory + +You can customize the local storage path for MinIO in one of the following ways: + +1. **Using the Setup Script**: During the configuration process, the setup script allows you to specify a custom path for MinIO's local data directory. Follow the prompts to set `MINIO_DATA_DIR` to your desired path. + +2. **Manually Editing the .env File**: Directly modify the `MINIO_DATA_DIR` environment variable in the `.env` file to point to a different directory for storing MinIO's data. + +### Access MinIO + +- **MinIO Server:** + + - If using Docker, the server will be accessible at `http://minio:9000`. + - If not using Docker, the server will be accessible at `http://localhost:9000`. + +- **MinIO Console (Web UI):** Access the console at `http://localhost:9001`. + + ![MinIO webUI login](public/markdown/images/mino-webui-login.png) + +### Create a Bucket + +After logging into the MinIO web UI, create a bucket with the name specified in your `.env` file under the `MINIO_BUCKET` variable. + +![MinIO create bucket](public/markdown/images/minio-create-bucket.png) + # Configuration It's important to configure Talawa-API to complete it's setup. @@ -541,6 +734,11 @@ This `.env` file must be populated with the following environment variables for | REDIS_PORT | Specifies the port of the active redis-server | | REDIS_PASSWORD(optional) | Used for authenticating the connection request to | | | a hosted redis-server | +| MINIO_ENDPOINT | Used for connecting talawa-api to the MinIO storage. | +| MINIO_ROOT_USER | Used to authenticate with the MinIO server. | +| MINIO_ROOT_PASSWORD | Used to authenticate with the MinIO server. | +| MINIO_BUCKET | Used for the bucket name in the MinIO storage. | +| MINIO_DATA_DIR | Defines the local directory path for MinIO storage. | The following sections will show you how to configure each of these parameters. @@ -708,7 +906,7 @@ We use `reCAPTCHA` for two factor authentication (2FA). Follow these steps: 1. Visit the [reCAPTCHA Key Generation](https://www.google.com/recaptcha/admin/create) URL. 1. Fill in the input blocks as shown in the screenshot: - ![Set up recaptcha page](public/markdown/images/recaptcha_set_up.webp) + ![Set up recaptcha page](public/markdown/images/recaptcha_set_up.png) 1. Click on `Submit` button. 1. Copy the generated `Secret Key` to variable named `RECAPTCHA_SECRET_KEY` in `.env` file. @@ -757,6 +955,40 @@ SMTP_SSL_TLS=true For more information on setting up a smtp server, here's a [useful article](https://sendgrid.com/blog/what-is-an-smtp-server/) +### Setting up MinIO configurations + +To use MinIO with Talawa-API, you need to set up the following environment variables in your .env file: + +``` +MINIO_ENDPOINT= +MINIO_ROOT_USER= +MINIO_ROOT_PASSWORD= +MINIO_BUCKET= +MINIO_DATA_DIR= +``` + +For example: + +``` +MINIO_ENDPOINT=http://localhost:9000 +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD=minioadminpassword +MINIO_BUCKET=talawa-bucket +MINIO_DATA_DIR=./data +``` + +Here are the configuration details: + +`MINIO_ENDPOINT`: URL where MinIO is hosted. Use http://minio:9000 for Docker setups, or http://localhost:9000 otherwise. + +`MINIO_ROOT_USER`: Root username for authenticating and managing MinIO resources. + +`MINIO_ROOT_PASSWORD`: Root password for authenticating with MinIO. Must be kept secure. + +`MINIO_BUCKET`: Name of the default bucket for storing files in MinIO. + +`MINIO_DATA_DIR`: Specifies the directory path where MinIO stores data locally. It is used to define the storage location for the MinIO server on the host machine. + ### Setting up Logger configurations 1. This is an optional setting @@ -1054,4 +1286,4 @@ You can run the tests for talawa-api using this command: ``` npm run test -``` +``` \ No newline at end of file diff --git a/README.md b/README.md index bdf939c79ff..d86dba227ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Talawa API -[💬 Join the community on Slack](https://github.com/PalisadoesFoundation/) +💬 Join the community on Slack from our [Palisadoes Foundation GitHub Home Page](https://github.com/PalisadoesFoundation) [![N|Solid](public/markdown/images/talawa-logo-lite-200x200.png)](https://github.com/PalisadoesFoundation/talawa-api) @@ -21,7 +21,7 @@ Core features include: `talawa` is based on the original `quito` code created by the [Palisadoes Foundation](http://www.palisadoes.org) as part of its annual Calico Challenge program. Calico provides paid summer internships for Jamaican university students to work on selected open source projects. They are mentored by software professionals and receive stipends based on the completion of predefined milestones. Calico was started in 2015. Visit [The Palisadoes Foundation's website](http://www.palisadoes.org/) for more details on its origin and activities. -## Table of Contents +## Table of Contents diff --git a/codegen.ts b/codegen.ts index 75bbf9f1c60..9a34f09b9bc 100644 --- a/codegen.ts +++ b/codegen.ts @@ -40,16 +40,13 @@ const config: CodegenConfig = { CheckIn: "../models/CheckIn#InterfaceCheckIn", - MessageChat: "../models/MessageChat#InterfaceMessageChat", - Comment: "../models/Comment#InterfaceComment", Community: "../models/Community#InterfaceCommunity", - DirectChat: "../models/DirectChat#InterfaceDirectChat", + Chat: "../models/Chat#InterfaceChat", - DirectChatMessage: - "../models/DirectChatMessage#InterfaceDirectChatMessage", + ChatMessage: "../models/ChatMessage#InterfaceChatMessage", Donation: "../models/Donation#InterfaceDonation", @@ -75,11 +72,6 @@ const config: CodegenConfig = { Group: "../models/Group#InterfaceGroup", - GroupChat: "../models/GroupChat#InterfaceGroupChat", - - GroupChatMessage: - "../models/GroupChatMessage#InterfaceGroupChatMessage", - // ImageHash: '../models/ImageHash#InterfaceImageHash', Language: "../models/Language#InterfaceLanguage", @@ -106,6 +98,9 @@ const config: CodegenConfig = { User: "../models/User#InterfaceUser", Venue: "../models/Venue#InterfaceVenue", + + VolunteerMembership: + "../models/VolunteerMembership#InterfaceVolunteerMembership", }, useTypeImports: true, diff --git a/config/vitestSetup.ts b/config/vitestSetup.ts index 44152f1a195..3be116e90cf 100644 --- a/config/vitestSetup.ts +++ b/config/vitestSetup.ts @@ -1,6 +1,5 @@ // FAIL LOUDLY on unhandled promise rejections / errors process.on("unhandledRejection", (reason) => { - // eslint-disable-next-line no-console console.log("FAILED TO HANDLE PROMISE REJECTION"); throw reason; }); diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index b435a1cb7a2..12907723b7f 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -7,6 +7,8 @@ services: - 27017:27017 volumes: - mongodb-data:/data/db + networks: + - talawa-network redis-stack-server: image: redis/redis-stack-server:latest @@ -14,6 +16,22 @@ services: - 6379:6379 volumes: - redis-data:/data/redis + networks: + - talawa-network + + minio: + image: minio/minio + ports: + - "9000:9000" + - "9001:9001" + environment: + - MINIO_ROOT_USER=${MINIO_ROOT_USER} + - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} + command: server /data --console-address ":9001" + volumes: + - minio-data:/data + networks: + - talawa-network talawa-api-dev: build: @@ -25,6 +43,7 @@ services: depends_on: - mongodb - redis-stack-server + - minio environment: - MONGO_DB_URL=mongodb://mongodb:27017 - REDIS_HOST=redis-stack-server @@ -38,7 +57,7 @@ services: - "80:80" - "443:443" volumes: - - $PWD/Caddyfile:/etc/caddy/Caddyfile + - ./Caddyfile:/etc/caddy/Caddyfile - $PWD/site:/srv - caddy_data:/data - caddy_config:/config @@ -48,3 +67,8 @@ volumes: redis-data: caddy_data: caddy_config: + minio-data: + +networks: + talawa-network: + driver: bridge diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml index ef2448c1048..08c49cdb821 100644 --- a/docker-compose.prod.yaml +++ b/docker-compose.prod.yaml @@ -1,5 +1,4 @@ -version: '3.8' - +version: "3.8" services: mongodb: image: mongo:latest @@ -7,14 +6,29 @@ services: - 27017:27017 volumes: - mongodb-data:/data/db - + networks: + - talawa-network redis-stack-server: image: redis/redis-stack-server:latest ports: - 6379:6379 volumes: - redis-data:/data/redis - + networks: + - talawa-network + minio: + image: minio/minio + ports: + - "9000:9000" + - "9001:9001" + environment: + - MINIO_ROOT_USER=${MINIO_ROOT_USER} + - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} + command: server /data --console-address ":9001" + volumes: + - minio-data:/data + networks: + - talawa-network talawa-api-prod-container: build: context: . @@ -24,11 +38,20 @@ services: depends_on: - mongodb - redis-stack-server + - minio environment: - MONGO_DB_URL=mongodb://mongodb:27017 - REDIS_HOST=redis-stack-server - REDIS_PORT=6379 + - MINIO_ENDPOINT=${MINIO_ENDPOINT} + networks: + - talawa-network volumes: mongodb-data: redis-data: + minio-data: + +networks: + talawa-network: + driver: bridge diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..16ddae464e3 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,165 @@ +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import tsdoc from "eslint-plugin-tsdoc"; +import _import from "eslint-plugin-import"; +import { fixupPluginRules } from "@eslint/compat"; +import globals from "globals"; +import tsParser from "@typescript-eslint/parser"; +import * as parser from "@graphql-eslint/eslint-plugin"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import js from "@eslint/js"; +import { FlatCompat } from "@eslint/eslintrc"; +import * as graphqlEslint from "@graphql-eslint/eslint-plugin"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended +}); + +export default [ + { + ignores: [ + "**/.github", + "**/.vscode", + "**/build", + "**/coverage", + "**/node_modules", + "src/types", + "docs/Schema.md", + ], + }, + ...compat.extends( + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + ), + { + plugins: { + "@typescript-eslint": typescriptEslint, + tsdoc, + import: fixupPluginRules(_import), + }, + + languageOptions: { + parser: tsParser, + globals: { + ...globals.node, + }, + }, + rules: { + "no-restricted-imports": [ + "error", + { + patterns: ["**/src/**"], + }, + ], + "import/no-duplicates": "error", + "tsdoc/syntax": "error", + "@typescript-eslint/ban-ts-comment": "error", + "@typescript-eslint/no-empty-object-type": "error", + "@typescript-eslint/no-unsafe-function-type": "error", + "@typescript-eslint/no-wrapper-object-types": "error", + "@typescript-eslint/no-duplicate-enum-values": "error", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-non-null-asserted-optional-chain": "error", + "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-var-requires": "error", + }, + }, + { + files: ["**/*.ts"], + + languageOptions: { + parser: tsParser, + ecmaVersion: "latest", + sourceType: "module", + + parserOptions: { + project: "./tsconfig.json", + tsconfigRootDir: ".", + }, + }, + + rules: { + "@typescript-eslint/array-type": "error", + "@typescript-eslint/consistent-type-assertions": "error", + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/explicit-function-return-type": "error", + + "@typescript-eslint/naming-convention": [ + "error", + { + selector: "interface", + format: ["PascalCase"], + prefix: ["Interface", "TestInterface"], + }, + { + selector: ["typeAlias", "typeLike", "enum"], + format: ["PascalCase"], + }, + { + selector: "typeParameter", + format: ["PascalCase"], + prefix: ["T"], + }, + { + selector: "variable", + format: ["camelCase", "UPPER_CASE"], + leadingUnderscore: "allow", + }, + { + selector: "parameter", + format: ["camelCase"], + leadingUnderscore: "allow", + }, + { + selector: "function", + format: ["camelCase"], + }, + { + selector: "memberLike", + modifiers: ["private"], + format: ["camelCase"], + leadingUnderscore: "require", + }, + { + selector: "variable", + modifiers: ["exported"], + format: null, + }, + ], + }, + }, + { + files: ["./src/typeDefs/**/*.ts"], + processor: "@graphql-eslint/graphql", + }, + { + files: ["./src/typeDefs/**/*.graphql"], + + plugins: { + "@graphql-eslint": graphqlEslint, + }, + + languageOptions: { + parser: graphqlEslint.parser, + }, + }, + { + files: ["tests/**/*"], + + rules: { + "no-restricted-imports": "off", + }, + }, + { + files: ["./src/index.ts", "./src/utilities/copyToClipboard.ts"], + + rules: { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-empty-function": "off", + }, + }, +]; diff --git a/locales/en.json b/locales/en.json index 1769c405376..15dcd74f547 100644 --- a/locales/en.json +++ b/locales/en.json @@ -29,10 +29,17 @@ "invalid.fileType": "Invalid file type", "invalid.refreshToken": "Invalid refresh token", "invalid.credentials": "Invalid credentials", + "invalid.timeoutRange": "Invalid timeout range", "registrant.alreadyExist": "Already registered for the event", "member.notFound": "Member not found", "registrant.alreadyUnregistered": "Already unregistered for the event", "translation.alreadyPresent": "Translation Already Present", "translation.notFound": "Translation not found", - "parameter.missing": "Missing Skip parameter. Set it to either 0 or some other value not found" + "parameter.missing": "Missing Skip parameter. Set it to either 0 or some other value not found", + "tag.alreadyExists": "A tag with the same name already exists at this level", + "invalid.contentType": "Invalid content type. Expected multipart/form-data", + "invalid.fieldFileName": "Invalid file input field name received", + "file.sizeExceeded": "File size exceeds the allowable limit", + "file.notFound": "File not found.", + "invalid.argument": "Invalid argument received" } diff --git a/locales/fr.json b/locales/fr.json index 39fea9082c8..07eb2be24f4 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -28,10 +28,17 @@ "invalid.fileType": "Type de fichier non valide", "invalid.refreshToken": "Jeton d'actualisation non valide", "invalid.credentials": "Informations d'identification non valides", + "invalid.timeoutRange": "Plage de temps d'attente non valide", "registrant.alreadyExist": "Déjà inscrit à l'événement", "member.notFound": "Membre introuvable", "registrant.alreadyUnregistered": "Déjà non inscrit à l'événement", "translation.alreadyPresent": "Traduction déjà présente", "translation.notFound": "Traduction introuvable", - "parameter.missing": "Paramètre de saut manquant. Réglez-le sur 0 ou sur une autre valeur" + "parameter.missing": "Paramètre de saut manquant. Réglez-le sur 0 ou sur une autre valeur", + "tag.alreadyExists": "Un tag avec le même nom existe déjà à ce niveau", + "invalid.contentType": "Type de contenu invalide. multipart/form-data attendu.", + "invalid.fieldFileName": "Nom de champ de fichier non valide reçu.", + "file.sizeExceeded": "La taille du fichier dépasse la limite autorisée.", + "file.notFound": "Fichier non trouvé.", + "invalid.argument": "Argument non valide reçu." } diff --git a/locales/hi.json b/locales/hi.json index 74d3372fb07..366c04895ff 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -29,10 +29,17 @@ "invalid.fileType": "अमान्य फ़ाइल प्रकार", "invalid.refreshToken": "अमान्य रीफ़्रेश टोकन", "invalid.credentials": "अवैध प्रत्यय पत्र", + "invalid.timeoutRange": "अमान्य टाइमआउट रेंज", "registrant.alreadyExist": "घटना के लिए पहले से पंजीकृत", "member.notFound": "सदस्य अनुपस्थित", "registrant.alreadyUnregistered": "घटना के लिए पहले से ही अपंजीकृत", "translation.alreadyPresent": "अनुवाद पहले से मौजूद है", "translation.notFound": "अनुवाद नहीं मिला", - "parameter.missing": "छोड़ें पैरामीटर मौजूद नहीं है. इसे 0 या किसी अन्य मान पर सेट करें" + "parameter.missing": "छोड़ें पैरामीटर मौजूद नहीं है. इसे 0 या किसी अन्य मान पर सेट करें", + "tag.alreadyExists": "इस स्तर पर समान नाम वाला टैग पहले से मौजूद है", + "invalid.contentType": "अमान्य सामग्री प्रकार। अपेक्षित है multipart/form-data।", + "invalid.fieldFileName": "अमान्य फ़ाइल इनपुट फ़ील्ड नाम प्राप्त हुआ।", + "file.sizeExceeded": "फ़ाइल का आकार अनुमत सीमा से अधिक है।", + "file.notFound": "फ़ाइल नहीं मिली।", + "invalid.argument": "अमान्य तर्क प्राप्त हुआ।" } diff --git a/locales/sp.json b/locales/sp.json index cec263813a7..59a5fdc8016 100644 --- a/locales/sp.json +++ b/locales/sp.json @@ -28,10 +28,17 @@ "invalid.fileType": "Tipo de archivo no válido", "invalid.refreshToken": "Token de actualización no válido", "invalid.credentials": "Credenciales no válidas", + "invalid.timeoutRange": "Rango de tiempo de espera no válido", "registrant.alreadyExist": "Ya inscrito para el evento", "member.notFound": "Miembro no encontrado", "registrant.alreadyUnregistered": "Ya no está registrado para el evento", "translation.alreadyPresent": "Traducción ya presente", "translation.notFound": "Traducción no encontrada", - "parameter.missing": "Falta el parámetro Omitir. Establézcalo en 0 o en algún otro valor" + "parameter.missing": "Falta el parámetro Omitir. Establézcalo en 0 o en algún otro valor", + "tag.alreadyExists": "Ya existe una etiqueta con el mismo nombre en este nivel", + "invalid.contentType": "Tipo de contenido no válido. Se esperaba multipart/form-data.", + "invalid.fieldFileName": "Se recibió un nombre de campo de archivo no válido.", + "file.sizeExceeded": "El tamaño del archivo supera el límite permitido.", + "file.notFound": "Archivo no encontrado.", + "invalid.argument": "Se recibió un argumento no válido." } diff --git a/locales/zh.json b/locales/zh.json index 16453c5236d..7593b1eb2a2 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -28,10 +28,17 @@ "invalid.fileType": "無效的文件類型", "invalid.refreshToken": "無效的刷新令牌", "invalid.credentials": "無效的憑據", + "invalid.timeoutRange": "无效的超时范围", "registrant.alreadyExist": "已经报名参加活动", "member.notFound": "未找到成员", "registrant.alreadyUnregistered": "已取消注册该活动", "translation.alreadyPresent": "翻译已经存在", "translation.notFound": "找不到翻译", - "parameter.missing": "缺少跳过参数。将其设置为 0 或其他值" + "parameter.missing": "缺少跳过参数。将其设置为 0 或其他值", + "tag.alreadyExists": "此级别已存在相同名称的标签", + "invalid.contentType": "内容类型无效。预期为 multipart/form-data。", + "invalid.fieldFileName": "收到无效的文件输入字段名称。", + "file.sizeExceeded": "文件大小超过允许的限制。", + "file.notFound": "文件未找到。", + "invalid.argument": "收到无效的参数。" } diff --git a/package-lock.json b/package-lock.json index 360901f3224..15f555b34ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,38 +9,39 @@ "version": "1.0.0", "license": "GNU General Public License v3.0", "dependencies": { - "@apollo/server": "^4.11.0", - "@faker-js/faker": "^8.2.0", - "@graphql-inspector/cli": "^5.0.6", - "@graphql-tools/resolvers-composition": "^7.0.1", + "@apollo/server": "^4.11.2", + "@aws-sdk/client-s3": "^3.691.0", + "@faker-js/faker": "^9.0.1", + "@graphql-inspector/cli": "^5.0.7", + "@graphql-tools/resolvers-composition": "^7.0.2", "@graphql-tools/schema": "^10.0.6", "@graphql-tools/utils": "^10.3.2", "@parcel/watcher": "^2.4.1", "@types/graphql-upload": "^16.0.5", - "@types/yargs": "^17.0.32", + "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.2.0", - "@typescript-eslint/parser": "^8.0.1", - "axios": "^1.7.4", + "@typescript-eslint/parser": "^8.11.0", + "axios": "^1.7.7", "bcryptjs": "^2.4.3", "bluebird": "3.7.2", "cls-hooked": "^4.2.2", "copy-paste": "^1.5.3", "cors": "^2.8.5", "cross-env": "^7.0.3", - "date-fns": "^3.3.1", + "date-fns": "^4.1.0", "dotenv": "^16.4.1", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^7.4.0", + "express-rate-limit": "^7.4.1", "graphql": "^16.9.0", "graphql-depth-limit": "^1.1.0", "graphql-scalars": "^1.20.1", "graphql-subscriptions": "^2.0.0", "graphql-tag": "^2.12.6", - "graphql-upload": "^16.0.2", - "graphql-voyager": "^2.0.0", + "graphql-upload": "^17.0.0", + "graphql-voyager": "^2.1.0", "graphql-ws": "^5.16.0", - "helmet": "^7.1.0", + "helmet": "^8.0.0", "i18n": "^0.15.1", "image-hash": "^5.3.1", "ioredis": "^5.4.1", @@ -52,59 +53,68 @@ "mongoose": "^8.3.2", "mongoose-paginate-v2": "^1.8.3", "morgan": "^1.10.0", + "multer": "^1.4.5-lts.1", "nanoid": "^5.0.7", - "nodemailer": "^6.9.14", + "nodemailer": "^6.9.16", "pm2": "^5.4.0", "redis": "^4.7.0", "rrule": "^2.8.1", - "typedoc-plugin-markdown": "^4.2.3", - "uuid": "^10.0.0", + "typedoc-plugin-markdown": "^4.2.7", + "uuid": "^11.0.1", "validator": "^13.12.0", - "winston": "^3.14.2", + "winston": "^3.15.0", "ws": "^8.18.0", "yargs": "^17.7.2", "zod": "^3.23.8", "zod-error": "^1.5.0" }, "devDependencies": { - "@graphql-codegen/cli": "^5.0.2", - "@graphql-codegen/typescript": "^4.0.9", + "@eslint/compat": "^1.1.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "^8.57.0", + "@graphql-codegen/cli": "^5.0.3", + "@graphql-codegen/typescript": "^4.1.0", "@graphql-codegen/typescript-resolvers": "^4.2.1", "@graphql-eslint/eslint-plugin": "^3.20.1", "@parcel/watcher": "^2.4.1", "@types/bcryptjs": "^2.4.6", - "@types/cls-hooked": "^4.3.8", + "@types/cls-hooked": "^4.3.9", "@types/copy-paste": "^1.1.30", "@types/cors": "^2.8.17", "@types/express": "^4.17.17", - "@types/express-rate-limit": "^6.0.0", + "@types/express-rate-limit": "^6.0.2", "@types/graphql-depth-limit": "^1.1.6", "@types/i18n": "^0.13.12", "@types/inquirer": "^9.0.7", - "@types/jsonwebtoken": "^9.0.5", - "@types/lodash": "^4.17.7", + "@types/jsonwebtoken": "^9.0.7", + "@types/lodash": "^4.17.13", "@types/mongoose-paginate-v2": "^1.6.5", "@types/morgan": "^1.9.9", - "@types/node": "^22.5.2", - "@types/nodemailer": "^6.4.15", + "@types/multer": "^1.4.12", + "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.16", + "@types/supertest": "^6.0.2", "@types/uuid": "^10.0.0", - "@types/validator": "^13.12.0", - "@vitest/coverage-v8": "^2.0.5", + "@types/validator": "^13.12.2", + "@vitest/coverage-v8": "^2.1.5", "cls-bluebird": "^2.1.0", - "concurrently": "^8.2.2", - "eslint": "^8.56.0", + "concurrently": "^9.0.1", + "eslint": "^9.10.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-tsdoc": "^0.3.0", "get-graphql-schema": "^2.1.2", - "graphql-markdown": "^7.0.0", - "husky": "^9.1.5", + "globals": "^15.12.0", + "graphql-markdown": "^7.3.0", + "husky": "^9.1.6", "lint-staged": "^15.2.10", + "nodemon": "^3.1.7", "prettier": "^3.3.3", "rimraf": "^6.0.1", - "tsx": "^4.19.0", + "supertest": "^7.0.0", + "tsx": "^4.19.1", "typescript": "^5.5.4", - "vitest": "^2.0.5" + "vitest": "^2.1.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -160,9 +170,9 @@ } }, "node_modules/@apollo/server": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.11.0.tgz", - "integrity": "sha512-SWDvbbs0wl2zYhKG6aGLxwTJ72xpqp0awb2lotNpfezd9VcAvzaUizzKQqocephin2uMoaA8MguoyBmgtPzNWw==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.11.2.tgz", + "integrity": "sha512-WUTHY7DDek8xAMn4Woa9Bl8duQUDzRYQkosX/d1DtCsBWESZyApR7ndnI5d6+W4KSTtqBHhJFkusEI7CWuIJXg==", "dependencies": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^1.1.1", @@ -180,7 +190,7 @@ "@types/node-fetch": "^2.6.1", "async-retry": "^1.2.1", "cors": "^2.8.5", - "express": "^4.17.1", + "express": "^4.21.1", "loglevel": "^1.6.8", "lru-cache": "^7.10.1", "negotiator": "^0.6.3", @@ -547,2989 +557,3311 @@ "node": ">=14" } }, - "node_modules/@babel/code-frame": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", - "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dependencies": { - "@babel/highlight": "^7.24.6", - "picocolors": "^1.0.0" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz", - "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", - "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/core": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", - "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.6", - "@babel/generator": "^7.24.6", - "@babel/helper-compilation-targets": "^7.24.6", - "@babel/helper-module-transforms": "^7.24.6", - "@babel/helpers": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/template": "^7.24.6", - "@babel/traverse": "^7.24.6", - "@babel/types": "^7.24.6", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" } }, - "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==" + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } }, - "node_modules/@babel/generator": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz", - "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dependencies": { - "@babel/types": "^7.24.6", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dependencies": { - "@babel/types": "^7.22.5" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", - "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dependencies": { - "@babel/compat-data": "^7.24.6", - "@babel/helper-validator-option": "^7.24.6", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.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==", - "dependencies": { - "yallist": "^3.0.2" + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "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==" - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz", - "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==", - "dev": true, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", - "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", - "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dependencies": { - "@babel/template": "^7.24.6", - "@babel/types": "^7.24.6" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", - "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dependencies": { - "@babel/types": "^7.24.6" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", - "dev": true, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dependencies": { - "@babel/types": "^7.23.0" + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", - "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dependencies": { - "@babel/types": "^7.24.6" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", - "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-module-imports": "^7.24.6", - "@babel/helper-simple-access": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "@babel/helper-validator-identifier": "^7.24.6" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "node_modules/@aws-sdk/client-s3": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.691.0.tgz", + "integrity": "sha512-GrcFakf5sZDSFtQGIPzT/5CTl9rLCsua0+yrmz/zidCvd7HFiwPrmyLQSv+MwgEUqHb4unnqUMSo2HKfkV3AIQ==", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.691.0", + "@aws-sdk/client-sts": "3.691.0", + "@aws-sdk/core": "3.691.0", + "@aws-sdk/credential-provider-node": "3.691.0", + "@aws-sdk/middleware-bucket-endpoint": "3.686.0", + "@aws-sdk/middleware-expect-continue": "3.686.0", + "@aws-sdk/middleware-flexible-checksums": "3.691.0", + "@aws-sdk/middleware-host-header": "3.686.0", + "@aws-sdk/middleware-location-constraint": "3.686.0", + "@aws-sdk/middleware-logger": "3.686.0", + "@aws-sdk/middleware-recursion-detection": "3.686.0", + "@aws-sdk/middleware-sdk-s3": "3.691.0", + "@aws-sdk/middleware-ssec": "3.686.0", + "@aws-sdk/middleware-user-agent": "3.691.0", + "@aws-sdk/region-config-resolver": "3.686.0", + "@aws-sdk/signature-v4-multi-region": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@aws-sdk/util-endpoints": "3.686.0", + "@aws-sdk/util-user-agent-browser": "3.686.0", + "@aws-sdk/util-user-agent-node": "3.691.0", + "@aws-sdk/xml-builder": "3.686.0", + "@smithy/config-resolver": "^3.0.10", + "@smithy/core": "^2.5.1", + "@smithy/eventstream-serde-browser": "^3.0.11", + "@smithy/eventstream-serde-config-resolver": "^3.0.8", + "@smithy/eventstream-serde-node": "^3.0.10", + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/hash-blob-browser": "^3.1.7", + "@smithy/hash-node": "^3.0.8", + "@smithy/hash-stream-node": "^3.1.7", + "@smithy/invalid-dependency": "^3.0.8", + "@smithy/md5-js": "^3.0.8", + "@smithy/middleware-content-length": "^3.0.10", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-retry": "^3.0.25", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/protocol-http": "^4.1.5", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.25", + "@smithy/util-defaults-mode-node": "^3.0.25", + "@smithy/util-endpoints": "^2.1.4", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "@smithy/util-stream": "^3.2.1", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.7", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "node_modules/@aws-sdk/client-sso": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.691.0.tgz", + "integrity": "sha512-bzp4ni6zGxwrlSWhG0MfOh57ORgzdUFlIc2JeQHLO9b6n0iNnG57ILHzo90sQxom6LfW1bXZrsKvYH3vAU8sdA==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.691.0", + "@aws-sdk/middleware-host-header": "3.686.0", + "@aws-sdk/middleware-logger": "3.686.0", + "@aws-sdk/middleware-recursion-detection": "3.686.0", + "@aws-sdk/middleware-user-agent": "3.691.0", + "@aws-sdk/region-config-resolver": "3.686.0", + "@aws-sdk/types": "3.686.0", + "@aws-sdk/util-endpoints": "3.686.0", + "@aws-sdk/util-user-agent-browser": "3.686.0", + "@aws-sdk/util-user-agent-node": "3.691.0", + "@smithy/config-resolver": "^3.0.10", + "@smithy/core": "^2.5.1", + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/hash-node": "^3.0.8", + "@smithy/invalid-dependency": "^3.0.8", + "@smithy/middleware-content-length": "^3.0.10", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-retry": "^3.0.25", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/protocol-http": "^4.1.5", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.25", + "@smithy/util-defaults-mode-node": "^3.0.25", + "@smithy/util-endpoints": "^2.1.4", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.691.0.tgz", + "integrity": "sha512-3njUhD4buM1RfigU6IXZ18/R9V5mbqNrAftgDabnNn4/V4Qly32nz+KQONXT5x0GqPszGhp+0mmwuLai9DxSrQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.691.0", + "@aws-sdk/credential-provider-node": "3.691.0", + "@aws-sdk/middleware-host-header": "3.686.0", + "@aws-sdk/middleware-logger": "3.686.0", + "@aws-sdk/middleware-recursion-detection": "3.686.0", + "@aws-sdk/middleware-user-agent": "3.691.0", + "@aws-sdk/region-config-resolver": "3.686.0", + "@aws-sdk/types": "3.686.0", + "@aws-sdk/util-endpoints": "3.686.0", + "@aws-sdk/util-user-agent-browser": "3.686.0", + "@aws-sdk/util-user-agent-node": "3.691.0", + "@smithy/config-resolver": "^3.0.10", + "@smithy/core": "^2.5.1", + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/hash-node": "^3.0.8", + "@smithy/invalid-dependency": "^3.0.8", + "@smithy/middleware-content-length": "^3.0.10", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-retry": "^3.0.25", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/protocol-http": "^4.1.5", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.25", + "@smithy/util-defaults-mode-node": "^3.0.25", + "@smithy/util-endpoints": "^2.1.4", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", - "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", - "dependencies": { - "@babel/types": "^7.24.6" + "@aws-sdk/client-sts": "^3.691.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.691.0.tgz", + "integrity": "sha512-Qmj2euPnmIni/eFSrc9LUkg52/2D487fTcKMwZh0ldHv4fD4ossuXX7AaDur8SD9Lc9EOxn/hXCsI644YnGwew==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.691.0", + "@aws-sdk/core": "3.691.0", + "@aws-sdk/credential-provider-node": "3.691.0", + "@aws-sdk/middleware-host-header": "3.686.0", + "@aws-sdk/middleware-logger": "3.686.0", + "@aws-sdk/middleware-recursion-detection": "3.686.0", + "@aws-sdk/middleware-user-agent": "3.691.0", + "@aws-sdk/region-config-resolver": "3.686.0", + "@aws-sdk/types": "3.686.0", + "@aws-sdk/util-endpoints": "3.686.0", + "@aws-sdk/util-user-agent-browser": "3.686.0", + "@aws-sdk/util-user-agent-node": "3.691.0", + "@smithy/config-resolver": "^3.0.10", + "@smithy/core": "^2.5.1", + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/hash-node": "^3.0.8", + "@smithy/invalid-dependency": "^3.0.8", + "@smithy/middleware-content-length": "^3.0.10", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-retry": "^3.0.25", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/protocol-http": "^4.1.5", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.25", + "@smithy/util-defaults-mode-node": "^3.0.25", + "@smithy/util-endpoints": "^2.1.4", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "node_modules/@aws-sdk/core": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.691.0.tgz", + "integrity": "sha512-5hyCj6gX92fXRf1kyfIpJetjVx0NxHbNmcLcrMy6oXuGNIBeJkMp+ZC6uJo3PsIjyPgGQSC++EhjLxpWiF/wHg==", + "dependencies": { + "@aws-sdk/types": "3.686.0", + "@smithy/core": "^2.5.1", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/property-provider": "^3.1.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/signature-v4": "^4.2.1", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-middleware": "^3.0.8", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", - "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.691.0.tgz", + "integrity": "sha512-c4Ip7tSNxt5VANVyryl6XjfEUCbm7f+iCUEfEWEezywll4DjNZ1N0l7nNmX4dDbwRAB42XH3rk5fbqBe0lXT8g==", "dependencies": { - "@babel/types": "^7.24.6" + "@aws-sdk/core": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", - "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.691.0.tgz", + "integrity": "sha512-RL2/d4DbUGeX8xKhXcwQvhAqd+WM3P87znSS5nEQA5pSwqeJsC3l2DCj+09yUM6I9n7nOppe5XephiiBpq190w==", + "dependencies": { + "@aws-sdk/core": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/property-provider": "^3.1.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", - "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.691.0.tgz", + "integrity": "sha512-NB5jbiBLAWD/oz2CHksKRHo+Q8KI8ljyZUDW091j7IDYEYZZ/c2jDkYWX7eGnJqKNZLxGtcc1B+yYJrE9xXnbQ==", + "dependencies": { + "@aws-sdk/core": "3.691.0", + "@aws-sdk/credential-provider-env": "3.691.0", + "@aws-sdk/credential-provider-http": "3.691.0", + "@aws-sdk/credential-provider-process": "3.691.0", + "@aws-sdk/credential-provider-sso": "3.691.0", + "@aws-sdk/credential-provider-web-identity": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/credential-provider-imds": "^3.2.5", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", - "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.691.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.691.0.tgz", + "integrity": "sha512-GjQvajKDz6nKWS1Cxdzz2Ecu9R8aojOhRIPAgnG62MG5BvlqDddanF6szcDVSYtlWx+cv2SZ6lDYjoHnDnideQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.691.0", + "@aws-sdk/credential-provider-http": "3.691.0", + "@aws-sdk/credential-provider-ini": "3.691.0", + "@aws-sdk/credential-provider-process": "3.691.0", + "@aws-sdk/credential-provider-sso": "3.691.0", + "@aws-sdk/credential-provider-web-identity": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/credential-provider-imds": "^3.2.5", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz", - "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.691.0.tgz", + "integrity": "sha512-tEoLkcxhF98aVHEyJ0n50rnNRewGUYYXszrNi8/sLh8enbDMWWByWReFPhNriE9oOdcrS5AKU7lCoY9i6zXQ3A==", "dependencies": { - "@babel/template": "^7.24.6", - "@babel/types": "^7.24.6" + "@aws-sdk/core": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", - "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.691.0.tgz", + "integrity": "sha512-CxEiF2LMesk93dG+fCglLyVS9m7rjkWAZFUSSbjW7YbJC0VDks83hQG8EsFv+Grl/kvFITEvU0NoiavI6hbDlw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.6", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@aws-sdk/client-sso": "3.691.0", + "@aws-sdk/core": "3.691.0", + "@aws-sdk/token-providers": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.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==", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.691.0.tgz", + "integrity": "sha512-54FgLnyWpSTlQ8/plZRFSXkI83wgPhJ4zqcX+n+K3BcGil4/Vsn/8+JQSY+6CA6JtDSqhpKAe54o+2DbDexsVg==", "dependencies": { - "color-convert": "^1.9.0" + "@aws-sdk/core": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.691.0" } }, - "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==", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.686.0.tgz", + "integrity": "sha512-6qCoWI73/HDzQE745MHQUYz46cAQxHCgy1You8MZQX9vHAQwqBnkcsb2hGp7S6fnQY5bNsiZkMWVQ/LVd2MNjg==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@aws-sdk/types": "3.686.0", + "@aws-sdk/util-arn-parser": "3.679.0", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "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==", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.686.0.tgz", + "integrity": "sha512-5yYqIbyhLhH29vn4sHiTj7sU6GttvLMk3XwCmBXjo2k2j3zHqFUwh9RyFGF9VY6Z392Drf/E/cl+qOGypwULpg==", "dependencies": { - "color-name": "1.1.3" + "@aws-sdk/types": "3.686.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.691.0.tgz", + "integrity": "sha512-jBKW3hZ8YpxlAecwuvMDWvs5tqu2I3BubptKeVJiwrEhNR1Yy3gtsZ1RnxCfGEEdVLS4fxc5JRF/jxPFnTT00Q==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-stream": "^3.2.1", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.8.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.686.0.tgz", + "integrity": "sha512-+Yc6rO02z+yhFbHmRZGvEw1vmzf/ifS9a4aBjJGeVVU+ZxaUvnk+IUZWrj4YQopUQ+bSujmMUzJLXSkbDq7yuw==", + "dependencies": { + "@aws-sdk/types": "3.686.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "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==", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.686.0.tgz", + "integrity": "sha512-pCLeZzt5zUGY3NbW4J/5x3kaHyJEji4yqtoQcUlJmkoEInhSxJ0OE8sTxAfyL3nIOF4yr6L2xdaLCqYgQT8Aog==", "dependencies": { - "has-flag": "^3.0.0" + "@aws-sdk/types": "3.686.0", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", - "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", - "bin": { - "parser": "bin/babel-parser.js" + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.686.0.tgz", + "integrity": "sha512-cX43ODfA2+SPdX7VRxu6gXk4t4bdVJ9pkktbfnkE5t27OlwNfvSGGhnHrQL8xTOFeyQ+3T+oowf26gf1OI+vIg==", + "dependencies": { + "@aws-sdk/types": "3.686.0", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", - "dev": true, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.686.0.tgz", + "integrity": "sha512-jF9hQ162xLgp9zZ/3w5RUNhmwVnXDBlABEUX8jCgzaFpaa742qR/KKtjjZQ6jMbQnP+8fOCSXFAVNMU+s6v81w==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@aws-sdk/types": "3.686.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.691.0.tgz", + "integrity": "sha512-JYtpQNy9/M0qgihu7RY9vdrtuF+71H3U/BK7EqtskM/ioNL7twAAonCmXA2NXxYjS9bG+/3hw3xZkWSWfYvYFA==", + "dependencies": { + "@aws-sdk/core": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@aws-sdk/util-arn-parser": "3.679.0", + "@smithy/core": "^2.5.1", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/signature-v4": "^4.2.1", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-stream": "^3.2.1", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", - "dev": true, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.686.0.tgz", + "integrity": "sha512-zJXml/CpVHFUdlGQqja87vNQ3rPB5SlDbfdwxlj1KBbjnRRwpBtxxmOlWRShg8lnVV6aIMGv95QmpIFy4ayqnQ==", "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" + "@aws-sdk/types": "3.686.0", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.691.0.tgz", + "integrity": "sha512-d1ieFuOw7Lh4PQguSWceOgX0B4YkZOuYPRZhlAbwx/LQayoZ7LDh//0bbdDdgDgKyNxCTN5EjdoCh/MAPaKIjQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@aws-sdk/core": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@aws-sdk/util-endpoints": "3.686.0", + "@smithy/core": "^2.5.1", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz", - "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==", - "dev": true, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.686.0.tgz", + "integrity": "sha512-6zXD3bSD8tcsMAVVwO1gO7rI1uy2fCD3czgawuPGPopeLiPpo6/3FoUWCQzk2nvEhj7p9Z4BbjwZGSlRkVrXTw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@aws-sdk/types": "3.686.0", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.691.0.tgz", + "integrity": "sha512-xCKaOoKJMTHxDWA82KTFOqAQUyGEKUqH+Est9aruR9alawbRx+qiLNt/+AhLrGT8IaFNycuD7P73V8yScJKE2g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@aws-sdk/middleware-sdk-s3": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/signature-v4": "^4.2.1", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", - "dev": true, + "node_modules/@aws-sdk/token-providers": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.691.0.tgz", + "integrity": "sha512-XtBnNUOzdezdC/7bFYAenrUQCZI5raHZ1F+7qWEbEDbshz4nR6v0MczVXkaPsSJ6mel0sQMhYs7b3Y/0yUkB6w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@aws-sdk/types": "3.686.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@aws-sdk/client-sso-oidc": "^3.691.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, + "node_modules/@aws-sdk/types": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", + "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", - "dev": true, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.679.0.tgz", + "integrity": "sha512-CwzEbU8R8rq9bqUFryO50RFBlkfufV9UfMArHPWlo+lmsC+NlSluHQALoj6Jkq3zf5ppn1CN0c1DDLrEqdQUXg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", - "dev": true, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.686.0.tgz", + "integrity": "sha512-7msZE2oYl+6QYeeRBjlDgxQUhq/XRky3cXE0FqLFs2muLS7XSuQEXkpOXB3R782ygAP6JX0kmBxPTLurRTikZg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@aws-sdk/types": "3.686.0", + "@smithy/types": "^3.6.0", + "@smithy/util-endpoints": "^2.1.4", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "dependencies": { + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", - "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", - "dev": true, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.686.0.tgz", + "integrity": "sha512-YiQXeGYZegF1b7B2GOR61orhgv79qmI0z7+Agm3NXLO6hGfVV3kFUJbXnjtH1BgWo5hbZYW7HQ2omGb3dnb6Lg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@aws-sdk/types": "3.686.0", + "@smithy/types": "^3.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.691.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.691.0.tgz", + "integrity": "sha512-n+g337W2W/S3Ju47vBNs970477WsLidmdQp1jaxFaBYjSV8l7Tm4dZNMtrq4AEvS+2ErkLpm9BmTiREoWR38Ag==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.691.0", + "@aws-sdk/types": "3.686.0", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", - "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", - "dev": true, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.686.0.tgz", + "integrity": "sha512-k0z5b5dkYSuOHY0AOZ4iyjcGBeVL9lWsQNF4+c+1oK3OW4fRWl/bNa1soMRMpangsHPzgyn/QkzuDbl7qR4qrw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", - "dev": true, + "node_modules/@babel/code-frame": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", + "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/highlight": "^7.24.6", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", - "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", - "dev": true, + "node_modules/@babel/compat-data": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz", + "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", + "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-compilation-targets": "^7.24.6", + "@babel/helper-module-transforms": "^7.24.6", + "@babel/helpers": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/template": "^7.24.6", + "@babel/traverse": "^7.24.6", + "@babel/types": "^7.24.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz", - "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==", - "dev": true, + "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==" + }, + "node_modules/@babel/generator": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz", + "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-flow": "^7.24.1" + "@babel/types": "^7.24.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", - "dev": true, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", + "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/compat-data": "^7.24.6", + "@babel/helper-validator-option": "^7.24.6", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { + "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==", + "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==" + }, + "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz", + "integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", - "dev": true, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", + "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", + "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", - "dev": true, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", + "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", - "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", - "dev": true, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", + "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", - "dev": true, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", + "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-module-imports": "^7.24.6", + "@babel/helper-simple-access": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", - "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" - }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { + "node_modules/@babel/helper-replace-supers": { "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", - "dev": true, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", + "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", + "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", "dependencies": { - "regenerator-runtime": "^0.14.0" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/template": { + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", - "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", - "dependencies": { - "@babel/code-frame": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/types": "^7.24.6" - }, + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", + "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse": { + "node_modules/@babel/helpers": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz", - "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz", + "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", "dependencies": { - "@babel/code-frame": "^7.24.6", - "@babel/generator": "^7.24.6", - "@babel/helper-environment-visitor": "^7.24.6", - "@babel/helper-function-name": "^7.24.6", - "@babel/helper-hoist-variables": "^7.24.6", - "@babel/helper-split-export-declaration": "^7.24.6", - "@babel/parser": "^7.24.6", - "@babel/types": "^7.24.6", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/types": { + "node_modules/@babel/highlight": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", - "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", "dependencies": { - "@babel/helper-string-parser": "^7.24.6", "@babel/helper-validator-identifier": "^7.24.6", - "to-fast-properties": "^2.0.0" + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@canvas/image-data": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz", - "integrity": "sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==" - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "optional": true, + "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==", + "dependencies": { + "color-convert": "^1.9.0" + }, "engines": { - "node": ">=0.1.90" + "node": ">=4" } }, - "node_modules/@cwasm/webp": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@cwasm/webp/-/webp-0.1.5.tgz", - "integrity": "sha512-ceIZQkyxK+s7mmItNcWqqHdOBiJAxYxTnrnPNgUNjldB1M9j+Bp/3eVIVwC8rUFyN/zoFwuT0331pyY3ackaNA==", + "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==", "dependencies": { - "@canvas/image-data": "^1.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "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==", "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "color-name": "1.1.3" } }, - "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, - "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/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" } }, - "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": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "dependencies": { - "@emotion/memoize": "^0.8.1" + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" } }, - "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/react": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz", - "integrity": "sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==", + "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==", "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.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" + "has-flag": "^3.0.0" }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } + "engines": { + "node": ">=4" } }, - "node_modules/@emotion/serialize": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz", - "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==", + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "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" + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "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.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz", - "integrity": "sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==", + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.5", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" + "engines": { + "node": ">=6.9.0" }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { - "react": ">=16.8.0" + "@babel/core": "^7.0.0-0" } }, - "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/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@envelop/core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.0.1.tgz", - "integrity": "sha512-wxA8EyE1fPnlbP0nC/SFI7uU8wSNf4YjxZhAPu0P63QbgIvqHtHsH4L3/u+rsTruzhk3OvNRgQyLsMfaR9uzAQ==", + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz", + "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==", + "dev": true, "dependencies": { - "@envelop/types": "5.0.0", - "tslib": "^2.5.0" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@envelop/types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@envelop/types/-/types-5.0.0.tgz", - "integrity": "sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", "dependencies": { - "tslib": "^2.5.0" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dev": true, - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "cpu": [ - "arm" - ], + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", "dev": true, - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", "dev": true, - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz", + "integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", + "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-classes/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, - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "cpu": [ - "arm" - ], + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", + "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz", + "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-flow": "^7.24.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "cpu": [ - "loong64" - ], + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "cpu": [ - "mips64el" - ], + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", - "cpu": [ - "riscv64" - ], + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-simple-access": "^7.22.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", - "cpu": [ - "s390x" - ], + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", + "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", "dev": true, - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", + "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", "dev": true, - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", "dev": true, - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", - "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", - "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "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==", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "node_modules/@babel/runtime": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@babel/template": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", + "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@babel/code-frame": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@babel/traverse": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz", + "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-function-name": "^7.24.6", + "@babel/helper-hoist-variables": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "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==", - "dependencies": { - "type-fest": "^0.20.2" - }, + "node_modules/@babel/traverse/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==", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { - "node": "*" + "node": ">=6.9.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==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "node_modules/@canvas/image-data": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz", + "integrity": "sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=0.1.90" } }, - "node_modules/@faker-js/faker": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", - "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0", - "npm": ">=6.14.13" + "node_modules/@cwasm/webp": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@cwasm/webp/-/webp-0.1.5.tgz", + "integrity": "sha512-ceIZQkyxK+s7mmItNcWqqHdOBiJAxYxTnrnPNgUNjldB1M9j+Bp/3eVIVwC8rUFyN/zoFwuT0331pyY3ackaNA==", + "dependencies": { + "@canvas/image-data": "^1.0.0" } }, - "node_modules/@graphql-codegen/add": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.2.tgz", - "integrity": "sha512-ouBkSvMFUhda5VoKumo/ZvsZM9P5ZTyDsI8LW18VxSNWOjrTeLXBWHG8Gfaai0HwhflPtCYVABbriEcOmrRShQ==", - "dev": true, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "tslib": "~2.6.0" - }, - "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" + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" } }, - "node_modules/@graphql-codegen/cli": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-5.0.2.tgz", - "integrity": "sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==", - "dev": true, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", "dependencies": { - "@babel/generator": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/types": "^7.18.13", - "@graphql-codegen/client-preset": "^4.2.2", - "@graphql-codegen/core": "^4.0.2", - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-tools/apollo-engine-loader": "^8.0.0", - "@graphql-tools/code-file-loader": "^8.0.0", - "@graphql-tools/git-loader": "^8.0.0", - "@graphql-tools/github-loader": "^8.0.0", - "@graphql-tools/graphql-file-loader": "^8.0.0", - "@graphql-tools/json-file-loader": "^8.0.0", - "@graphql-tools/load": "^8.0.0", - "@graphql-tools/prisma-loader": "^8.0.0", - "@graphql-tools/url-loader": "^8.0.0", - "@graphql-tools/utils": "^10.0.0", - "@whatwg-node/fetch": "^0.8.0", - "chalk": "^4.1.0", - "cosmiconfig": "^8.1.3", - "debounce": "^1.2.0", - "detect-indent": "^6.0.0", - "graphql-config": "^5.0.2", - "inquirer": "^8.0.0", - "is-glob": "^4.0.1", - "jiti": "^1.17.1", - "json-to-pretty-yaml": "^1.2.2", - "listr2": "^4.0.5", - "log-symbols": "^4.0.0", - "micromatch": "^4.0.5", - "shell-quote": "^1.7.3", - "string-env-interpolation": "^1.0.1", - "ts-log": "^2.2.3", - "tslib": "^2.4.0", - "yaml": "^2.3.1", - "yargs": "^17.0.0" - }, - "bin": { - "gql-gen": "cjs/bin.js", - "graphql-code-generator": "cjs/bin.js", - "graphql-codegen": "cjs/bin.js", - "graphql-codegen-esm": "esm/bin.js" + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "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/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { - "@parcel/watcher": "^2.1.0", - "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" + "react": ">=16.8.0" }, "peerDependenciesMeta": { - "@parcel/watcher": { + "@types/react": { "optional": true } } }, - "node_modules/@graphql-codegen/client-preset": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.2.4.tgz", - "integrity": "sha512-k1c8v2YxJhhITGQGxViG9asLAoop9m7X9duU7Zztqjc98ooxsUzXICfvAWsH3mLAUibXAx4Ax6BPzKsTtQmBPg==", - "dev": true, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/template": "^7.20.7", - "@graphql-codegen/add": "^5.0.2", - "@graphql-codegen/gql-tag-operations": "4.0.6", - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/typed-document-node": "^5.0.6", - "@graphql-codegen/typescript": "^4.0.6", - "@graphql-codegen/typescript-operations": "^4.2.0", - "@graphql-codegen/visitor-plugin-common": "^5.1.0", - "@graphql-tools/documents": "^1.0.0", - "@graphql-tools/utils": "^10.0.0", - "@graphql-typed-document-node/core": "3.2.0", - "tslib": "~2.6.0" - }, - "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" + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" } }, - "node_modules/@graphql-codegen/core": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-4.0.2.tgz", - "integrity": "sha512-IZbpkhwVqgizcjNiaVzNAzm/xbWT6YnGgeOLwVjm4KbJn3V2jchVtuzHH09G5/WkkLSk2wgbXNdwjM41JxO6Eg==", - "dev": true, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.0.0", - "tslib": "~2.6.0" + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" }, "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" + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@graphql-codegen/gql-tag-operations": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.6.tgz", - "integrity": "sha512-y6iXEDpDNjwNxJw3WZqX1/Znj0QHW7+y8O+t2V8qvbTT+3kb2lr9ntc8By7vCr6ctw9tXI4XKaJgpTstJDOwFA==", - "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/visitor-plugin-common": "5.1.0", - "@graphql-tools/utils": "^10.0.0", - "auto-bind": "~4.0.0", - "tslib": "~2.6.0" - }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", "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" + "react": ">=16.8.0" } }, - "node_modules/@graphql-codegen/plugin-helpers": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.4.tgz", - "integrity": "sha512-MOIuHFNWUnFnqVmiXtrI+4UziMTYrcquljaI5f/T/Bc7oO7sXcfkAvgkNWEEi9xWreYwvuer3VHCuPI/lAFWbw==", - "dev": true, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@envelop/core": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.0.2.tgz", + "integrity": "sha512-tVL6OrMe6UjqLosiE+EH9uxh2TQC0469GwF4tE014ugRaDDKKVWwFwZe0TBMlcyHKh5MD4ZxktWo/1hqUxIuhw==", "dependencies": { - "@graphql-tools/utils": "^10.0.0", - "change-case-all": "1.0.15", - "common-tags": "1.8.2", - "import-from": "4.0.0", - "lodash": "~4.17.0", - "tslib": "~2.6.0" + "@envelop/types": "5.0.0", + "tslib": "^2.5.0" }, - "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" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@graphql-codegen/schema-ast": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-4.0.2.tgz", - "integrity": "sha512-5mVAOQQK3Oz7EtMl/l3vOQdc2aYClUzVDHHkMvZlunc+KlGgl81j8TLa+X7ANIllqU4fUEsQU3lJmk4hXP6K7Q==", - "dev": true, + "node_modules/@envelop/types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-5.0.0.tgz", + "integrity": "sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==", "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-tools/utils": "^10.0.0", - "tslib": "~2.6.0" + "tslib": "^2.5.0" }, - "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" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@graphql-codegen/typed-document-node": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.6.tgz", - "integrity": "sha512-US0J95hOE2/W/h42w4oiY+DFKG7IetEN1mQMgXXeat1w6FAR5PlIz4JrRrEkiVfVetZ1g7K78SOwBD8/IJnDiA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/visitor-plugin-common": "5.1.0", - "auto-bind": "~4.0.0", - "change-case-all": "1.0.15", - "tslib": "~2.6.0" - }, - "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" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-codegen/typescript": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.9.tgz", - "integrity": "sha512-0O35DMR4d/ctuHL1Zo6mRUUzp0BoszKfeWsa6sCm/g70+S98+hEfTwZNDkQHylLxapiyjssF9uw/F+sXqejqLw==", + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/schema-ast": "^4.0.2", - "@graphql-codegen/visitor-plugin-common": "5.3.1", - "auto-bind": "~4.0.0", - "tslib": "~2.6.0" - }, - "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-codegen/typescript-operations": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.2.0.tgz", - "integrity": "sha512-lmuwYb03XC7LNRS8oo9M4/vlOrq/wOKmTLBHlltK2YJ1BO/4K/Q9Jdv/jDmJpNydHVR1fmeF4wAfsIp1f9JibA==", + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-codegen/typescript": "^4.0.6", - "@graphql-codegen/visitor-plugin-common": "5.1.0", - "auto-bind": "~4.0.0", - "tslib": "~2.6.0" - }, - "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" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-codegen/typescript-resolvers": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-4.2.1.tgz", - "integrity": "sha512-q/ggqNSKNGG9bn49DdZrw2KokagDZmzl1EpxIfzmpHrPa3XaCLfxQuNNEUhqEXtJzQZtLfuYvGy1y+MrTU8WnA==", + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/typescript": "^4.0.9", - "@graphql-codegen/visitor-plugin-common": "5.3.1", - "@graphql-tools/utils": "^10.0.0", - "auto-bind": "~4.0.0", - "tslib": "~2.6.0" - }, - "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" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-codegen/typescript-resolvers/node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.1.tgz", - "integrity": "sha512-MktoBdNZhSmugiDjmFl1z6rEUUaqyxtFJYWnDilE7onkPgyw//O0M+TuPBJPBWdyV6J2ond0Hdqtq+rkghgSIQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-tools/optimize": "^2.0.0", - "@graphql-tools/relay-operation-optimizer": "^7.0.0", - "@graphql-tools/utils": "^10.0.0", - "auto-bind": "~4.0.0", - "change-case-all": "1.0.15", - "dependency-graph": "^0.11.0", - "graphql-tag": "^2.11.0", - "parse-filepath": "^1.0.2", - "tslib": "~2.6.0" - }, - "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" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-codegen/typescript/node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.1.tgz", - "integrity": "sha512-MktoBdNZhSmugiDjmFl1z6rEUUaqyxtFJYWnDilE7onkPgyw//O0M+TuPBJPBWdyV6J2ond0Hdqtq+rkghgSIQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-tools/optimize": "^2.0.0", - "@graphql-tools/relay-operation-optimizer": "^7.0.0", - "@graphql-tools/utils": "^10.0.0", - "auto-bind": "~4.0.0", - "change-case-all": "1.0.15", - "dependency-graph": "^0.11.0", - "graphql-tag": "^2.11.0", - "parse-filepath": "^1.0.2", - "tslib": "~2.6.0" - }, - "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" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.1.0.tgz", - "integrity": "sha512-eamQxtA9bjJqI2lU5eYoA1GbdMIRT2X8m8vhWYsVQVWD3qM7sx/IqJU0kx0J3Vd4/CSd36BzL6RKwksibytDIg==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@graphql-codegen/plugin-helpers": "^5.0.3", - "@graphql-tools/optimize": "^2.0.0", - "@graphql-tools/relay-operation-optimizer": "^7.0.0", - "@graphql-tools/utils": "^10.0.0", - "auto-bind": "~4.0.0", - "change-case-all": "1.0.15", - "dependency-graph": "^0.11.0", - "graphql-tag": "^2.11.0", - "parse-filepath": "^1.0.2", - "tslib": "~2.6.0" - }, - "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" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.20.1.tgz", - "integrity": "sha512-RbwVlz1gcYG62sECR1u0XqMh8w5e5XMCCZoMvPQ3nJzEBCTfXLGX727GBoRmSvY1x4gJmqNZ1lsOX7lZY14RIw==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@graphql-tools/code-file-loader": "^7.3.6", - "@graphql-tools/graphql-tag-pluck": "^7.3.6", - "@graphql-tools/utils": "^9.0.0", - "chalk": "^4.1.2", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", - "graphql-config": "^4.4.0", - "graphql-depth-limit": "^1.1.0", - "lodash.lowercase": "^4.3.0", - "tslib": "^2.4.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=12" - }, - "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-eslint/eslint-plugin/node_modules/@graphql-tools/batch-execute": { - "version": "8.5.22", - "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-8.5.22.tgz", - "integrity": "sha512-hcV1JaY6NJQFQEwCKrYhpfLK8frSXDbtNMoTur98u10Cmecy1zrqNKSqhEyGetpgHxaJRqszGzKeI3RuroDN6A==", + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "dataloader": "^2.2.2", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/code-file-loader": { - "version": "7.3.23", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-7.3.23.tgz", - "integrity": "sha512-8Wt1rTtyTEs0p47uzsPJ1vAtfAx0jmxPifiNdmo9EOCuUPyQGEbMaik/YkqZ7QUFIEYEQu+Vgfo8tElwOPtx5Q==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@graphql-tools/graphql-tag-pluck": "7.5.2", - "@graphql-tools/utils": "^9.2.1", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/delegate": { - "version": "9.0.35", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-9.0.35.tgz", - "integrity": "sha512-jwPu8NJbzRRMqi4Vp/5QX1vIUeUPpWmlQpOkXQD2r1X45YsVceyUUBnktCrlJlDB4jPRVy7JQGwmYo3KFiOBMA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@graphql-tools/batch-execute": "^8.5.22", - "@graphql-tools/executor": "^0.0.20", - "@graphql-tools/schema": "^9.0.19", - "@graphql-tools/utils": "^9.2.1", - "dataloader": "^2.2.2", - "tslib": "^2.5.0", - "value-or-promise": "^1.0.12" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-0.0.20.tgz", - "integrity": "sha512-GdvNc4vszmfeGvUqlcaH1FjBoguvMYzxAfT6tDd4/LgwymepHhinqLNA5otqwVLW+JETcDaK7xGENzFomuE6TA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "@graphql-typed-document-node/core": "3.2.0", - "@repeaterjs/repeater": "^3.0.4", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor-graphql-ws": { - "version": "0.0.14", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-0.0.14.tgz", - "integrity": "sha512-P2nlkAsPZKLIXImFhj0YTtny5NQVGSsKnhi7PzXiaHSXc6KkzqbWZHKvikD4PObanqg+7IO58rKFpGXP7eeO+w==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "@repeaterjs/repeater": "3.0.4", - "@types/ws": "^8.0.0", - "graphql-ws": "5.12.1", - "isomorphic-ws": "5.0.0", - "tslib": "^2.4.0", - "ws": "8.13.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor-http": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-0.1.10.tgz", - "integrity": "sha512-hnAfbKv0/lb9s31LhWzawQ5hghBfHS+gYWtqxME6Rl0Aufq9GltiiLBcl7OVVOnkLF0KhwgbYP1mB5VKmgTGpg==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "@repeaterjs/repeater": "^3.0.4", - "@whatwg-node/fetch": "^0.8.1", - "dset": "^3.1.2", - "extract-files": "^11.0.0", - "meros": "^1.2.1", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor-legacy-ws": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-0.0.11.tgz", - "integrity": "sha512-4ai+NnxlNfvIQ4c70hWFvOZlSUN8lt7yc+ZsrwtNFbFPH/EroIzFMapAxM9zwyv9bH38AdO3TQxZ5zNxgBdvUw==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "@types/ws": "^8.0.0", - "isomorphic-ws": "5.0.0", - "tslib": "^2.4.0", - "ws": "8.13.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/graphql-file-loader": { - "version": "7.5.17", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.5.17.tgz", - "integrity": "sha512-hVwwxPf41zOYgm4gdaZILCYnKB9Zap7Ys9OhY1hbwuAuC4MMNY9GpUjoTU3CQc3zUiPoYStyRtUGkHSJZ3HxBw==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "@graphql-tools/import": "6.7.18", - "@graphql-tools/utils": "^9.2.1", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/import": { - "version": "6.7.18", - "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.7.18.tgz", - "integrity": "sha512-XQDdyZTp+FYmT7as3xRWH/x8dx0QZA2WZqfMF5EWb36a0PiH7WwlRQYIdyYXj8YCLpiWkeBXgBRHmMnwEYR8iQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "resolve-from": "5.0.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/json-file-loader": { - "version": "7.4.18", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-7.4.18.tgz", - "integrity": "sha512-AJ1b6Y1wiVgkwsxT5dELXhIVUPs/u3VZ8/0/oOtpcoyO/vAeM5rOvvWegzicOOnQw8G45fgBRMkkRfeuwVt6+w==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/load": { - "version": "7.8.14", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-7.8.14.tgz", - "integrity": "sha512-ASQvP+snHMYm+FhIaLxxFgVdRaM0vrN9wW2BKInQpktwWTXVyk+yP5nQUCEGmn0RTdlPKrffBaigxepkEAJPrg==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@graphql-tools/schema": "^9.0.18", - "@graphql-tools/utils": "^9.2.1", - "p-limit": "3.1.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/merge": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", - "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/schema": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", - "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@graphql-tools/merge": "^8.4.1", - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/url-loader": { - "version": "7.17.18", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-7.17.18.tgz", - "integrity": "sha512-ear0CiyTj04jCVAxi7TvgbnGDIN2HgqzXzwsfcqiVg9cvjT40NcMlZ2P1lZDgqMkZ9oyLTV8Bw6j+SyG6A+xPw==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@ardatan/sync-fetch": "^0.0.1", - "@graphql-tools/delegate": "^9.0.31", - "@graphql-tools/executor-graphql-ws": "^0.0.14", - "@graphql-tools/executor-http": "^0.1.7", - "@graphql-tools/executor-legacy-ws": "^0.0.11", - "@graphql-tools/utils": "^9.2.1", - "@graphql-tools/wrap": "^9.4.2", - "@types/ws": "^8.0.0", - "@whatwg-node/fetch": "^0.8.0", - "isomorphic-ws": "^5.0.0", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.11", - "ws": "^8.12.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/wrap": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-9.4.2.tgz", - "integrity": "sha512-DFcd9r51lmcEKn0JW43CWkkI2D6T9XI1juW/Yo86i04v43O9w2/k4/nx2XTJv4Yv+iXwUw7Ok81PGltwGJSDSA==", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", "dependencies": { - "@graphql-tools/delegate": "^9.0.31", - "@graphql-tools/schema": "^9.0.18", - "@graphql-tools/utils": "^9.2.1", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/@repeaterjs/repeater": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz", - "integrity": "sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==", - "dev": true + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/brace-expansion": { + "node_modules/@eslint/compat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.1.1.tgz", + "integrity": "sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/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, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/cosmiconfig": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz", - "integrity": "sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==", - "dev": true, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=14" + "node": "*" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/graphql-config": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-4.5.0.tgz", - "integrity": "sha512-x6D0/cftpLUJ0Ch1e5sj1TZn6Wcxx4oMfmhaG9shM0DKajA9iR+j1z86GSTQ19fShbGvrSSvbIQsHku6aQ6BBw==", + "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": { - "@graphql-tools/graphql-file-loader": "^7.3.7", - "@graphql-tools/json-file-loader": "^7.3.7", - "@graphql-tools/load": "^7.5.5", - "@graphql-tools/merge": "^8.2.6", - "@graphql-tools/url-loader": "^7.9.7", - "@graphql-tools/utils": "^9.0.0", - "cosmiconfig": "8.0.0", - "jiti": "1.17.1", - "minimatch": "4.2.3", - "string-env-interpolation": "1.0.1", - "tslib": "^2.4.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "cosmiconfig-toml-loader": "^1.0.0", - "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "cosmiconfig-toml-loader": { - "optional": true - } + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/graphql-ws": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.12.1.tgz", - "integrity": "sha512-umt4f5NnMK46ChM2coO36PTFhHouBrK9stWWBczERguwYrGnPNxJ9dimU6IyOBfOkC6Izhkg4H8+F51W/8CYDg==", + "node_modules/@eslint/eslintrc/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, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": ">=0.11 <=16" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/jiti": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.17.1.tgz", - "integrity": "sha512-NZIITw8uZQFuzQimqjUxIrIcEdxYDFIe/0xYfIlVXTkiBjjyBEvgasj5bb0/cHtPRD/NziPbT312sFrkI5ALpw==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "bin": { - "jiti": "bin/jiti.js" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/minimatch": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.3.tgz", - "integrity": "sha512-lIUdtK5hdofgCTu3aT0sOaHsYR37viUuIc0rwnnDXImbwFRcumyLMeZaM0t0I/fgxS6s6JMfu0rLD1Wz9pv1ng==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/@graphql-eslint/eslint-plugin/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@graphql-inspector/audit-command": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-inspector/audit-command/-/audit-command-5.0.6.tgz", - "integrity": "sha512-XfQIKoQj9TTNjEQOKUzTXVhjdJ97zJAqQi4M17OiYKsS8HpiSTmbPEzs+3GcJ/B3OZz+BtW4Oh0OGHTgImDzuw==", - "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/core": "6.1.0", - "@graphql-inspector/logger": "5.0.1", - "@graphql-tools/utils": "10.2.1", - "cli-table3": "0.6.3", - "tslib": "2.6.2" - }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "license": "Apache-2.0", "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@graphql-inspector/audit-command/node_modules/@graphql-tools/utils": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", - "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "license": "Apache-2.0", "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "cross-inspect": "1.0.0", - "dset": "^3.1.2", - "tslib": "^2.4.0" + "levn": "^0.4.1" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@graphql-inspector/cli": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-inspector/cli/-/cli-5.0.6.tgz", - "integrity": "sha512-yawOs8fY9AC6eBeeH1CNw1DyAQmBe9dNvAMjS+bG1k9haSn7erzfeWWkoPbqYcFty6FWLlHwVwA00nfpOdJhzQ==", - "dependencies": { - "@babel/core": "7.24.6", - "@graphql-inspector/audit-command": "5.0.6", - "@graphql-inspector/code-loader": "5.0.1", - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/config": "4.0.2", - "@graphql-inspector/coverage-command": "6.1.0", - "@graphql-inspector/diff-command": "5.0.6", - "@graphql-inspector/docs-command": "5.0.4", - "@graphql-inspector/git-loader": "5.0.1", - "@graphql-inspector/github-loader": "5.0.1", - "@graphql-inspector/graphql-loader": "5.0.1", - "@graphql-inspector/introspect-command": "5.0.6", - "@graphql-inspector/json-loader": "5.0.1", - "@graphql-inspector/loaders": "4.0.5", - "@graphql-inspector/serve-command": "5.0.5", - "@graphql-inspector/similar-command": "5.0.6", - "@graphql-inspector/url-loader": "5.0.1", - "@graphql-inspector/validate-command": "5.0.6", - "tslib": "2.6.2", - "yargs": "17.7.2" - }, - "bin": { - "graphql-inspector": "cjs/index.js" - }, + "node_modules/@faker-js/faker": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.1.tgz", + "integrity": "sha512-4mDeYIgM3By7X6t5E6eYwLAa+2h4DeZDF7thhzIg6XB76jeEvMwadYAMCFJL/R4AnEBcAUO9+gL0vhy3s+qvZA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "node": ">=18.0.0", + "npm": ">=9.0.0" } }, - "node_modules/@graphql-inspector/code-loader": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@graphql-inspector/code-loader/-/code-loader-5.0.1.tgz", - "integrity": "sha512-kdyP76g0QrtOFRda67+aNshSf0PXYyGJLiGxoVBogpAbkzDRhZQZAsdQVKP0tdEQAn4w0zN6VBdmpF/PAeBO5A==", + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", "dependencies": { - "@graphql-tools/code-file-loader": "8.1.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "@floating-ui/utils": "^0.2.8" } }, - "node_modules/@graphql-inspector/commands": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@graphql-inspector/commands/-/commands-5.0.4.tgz", - "integrity": "sha512-m6SzYxjkKhor7pV33r1FSL2Wq/epzeWDE1cfPT/eFJ4qKavTBcglr+Vpien6PK1a2vy69GhviFhMoJEakrlZMA==", + "node_modules/@floating-ui/dom": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", + "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", "dependencies": { - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { - "@graphql-inspector/config": "^4.0.0", - "@graphql-inspector/loaders": "^4.0.0", - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", - "yargs": "17.7.2" + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, - "node_modules/@graphql-inspector/config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-inspector/config/-/config-4.0.2.tgz", - "integrity": "sha512-fnIwVpGM5AtTr4XyV8NJkDnwpXxZSBzi3BopjuXwBPXXD1F3tcVkCKNT6/5WgUQGfNPskBVbitcOPtM4hIYAOQ==", + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + }, + "node_modules/@graphql-codegen/add": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.3.tgz", + "integrity": "sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==", + "dev": true, "dependencies": { - "tslib": "2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@graphql-codegen/plugin-helpers": "^5.0.3", + "tslib": "~2.6.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/core": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@graphql-inspector/core/-/core-6.1.0.tgz", - "integrity": "sha512-5/kqD5330duUsfMBfhMc0iVld76JwSKTkKi7aOr1x9MvSnP8p1anQo7BCNZ5VY9+EvWn4njHbkNfdS/lrqsi+A==", + "node_modules/@graphql-codegen/cli": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-5.0.3.tgz", + "integrity": "sha512-ULpF6Sbu2d7vNEOgBtE9avQp2oMgcPY/QBYcCqk0Xru5fz+ISjcovQX29V7CS7y5wWBRzNLoXwJQGeEyWbl05g==", + "dev": true, "dependencies": { - "dependency-graph": "1.0.0", - "object-inspect": "1.13.1", - "tslib": "2.6.2" + "@babel/generator": "^7.18.13", + "@babel/template": "^7.18.10", + "@babel/types": "^7.18.13", + "@graphql-codegen/client-preset": "^4.4.0", + "@graphql-codegen/core": "^4.0.2", + "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-tools/apollo-engine-loader": "^8.0.0", + "@graphql-tools/code-file-loader": "^8.0.0", + "@graphql-tools/git-loader": "^8.0.0", + "@graphql-tools/github-loader": "^8.0.0", + "@graphql-tools/graphql-file-loader": "^8.0.0", + "@graphql-tools/json-file-loader": "^8.0.0", + "@graphql-tools/load": "^8.0.0", + "@graphql-tools/prisma-loader": "^8.0.0", + "@graphql-tools/url-loader": "^8.0.0", + "@graphql-tools/utils": "^10.0.0", + "@whatwg-node/fetch": "^0.9.20", + "chalk": "^4.1.0", + "cosmiconfig": "^8.1.3", + "debounce": "^1.2.0", + "detect-indent": "^6.0.0", + "graphql-config": "^5.1.1", + "inquirer": "^8.0.0", + "is-glob": "^4.0.1", + "jiti": "^1.17.1", + "json-to-pretty-yaml": "^1.2.2", + "listr2": "^4.0.5", + "log-symbols": "^4.0.0", + "micromatch": "^4.0.5", + "shell-quote": "^1.7.3", + "string-env-interpolation": "^1.0.1", + "ts-log": "^2.2.3", + "tslib": "^2.4.0", + "yaml": "^2.3.1", + "yargs": "^17.0.0" + }, + "bin": { + "gql-gen": "cjs/bin.js", + "graphql-code-generator": "cjs/bin.js", + "graphql-codegen": "cjs/bin.js", + "graphql-codegen-esm": "esm/bin.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "@parcel/watcher": "^2.1.0", + "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" + }, + "peerDependenciesMeta": { + "@parcel/watcher": { + "optional": true + } } }, - "node_modules/@graphql-inspector/core/node_modules/dependency-graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", - "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "node_modules/@graphql-codegen/cli/node_modules/@whatwg-node/fetch": { + "version": "0.9.22", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.22.tgz", + "integrity": "sha512-+RIBffgoaRlWV9cKV6wAX71sbeoU2APOI3G13ZRMkabYHwkvDMeZDTyxJcsMXA5CpieJ7NFXF9Xyu72jwvdzqA==", + "dev": true, + "dependencies": { + "@whatwg-node/node-fetch": "^0.5.27", + "urlpattern-polyfill": "^10.0.0" + }, "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/@graphql-inspector/coverage-command": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@graphql-inspector/coverage-command/-/coverage-command-6.1.0.tgz", - "integrity": "sha512-c5/nERSACMkVkjlKF6ggUwI4nuKTyO991fqQiM9Dm36Heahu1BsH5BjtHWY6zlOg5b7e0v7X0+wqsLjNUsYGEA==", + "node_modules/@graphql-codegen/cli/node_modules/@whatwg-node/node-fetch": { + "version": "0.5.27", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.27.tgz", + "integrity": "sha512-0OaMj5W4fzWimRSFq07qFiWfquaUMNB+695GwE76LYKVuah+jwCdzSgsIOtwPkiyJ35w0XGhXmJPiIJCdLwopg==", + "dev": true, "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/core": "6.1.0", - "@graphql-inspector/logger": "5.0.1", - "@graphql-tools/utils": "10.2.1", - "tslib": "2.6.2" + "@kamilkisiela/fast-url-parser": "^1.1.4", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "tslib": "^2.6.3" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/coverage-command/node_modules/@graphql-tools/utils": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", - "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", + "node_modules/@graphql-codegen/cli/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/@graphql-codegen/cli/node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, + "node_modules/@graphql-codegen/client-preset": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.5.0.tgz", + "integrity": "sha512-0fFGSjpDhB7Jp6v+FQWDIeNJhL8VEiy3zeazyus3mGUELPaRQsoos2NczkDWnyMjSB1NHn4GrI53DB4TXkTAog==", + "dev": true, "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "cross-inspect": "1.0.0", - "dset": "^3.1.2", - "tslib": "^2.4.0" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7", + "@graphql-codegen/add": "^5.0.3", + "@graphql-codegen/gql-tag-operations": "4.0.11", + "@graphql-codegen/plugin-helpers": "^5.1.0", + "@graphql-codegen/typed-document-node": "^5.0.11", + "@graphql-codegen/typescript": "^4.1.1", + "@graphql-codegen/typescript-operations": "^4.3.1", + "@graphql-codegen/visitor-plugin-common": "^5.5.0", + "@graphql-tools/documents": "^1.0.0", + "@graphql-tools/utils": "^10.0.0", + "@graphql-typed-document-node/core": "3.2.0", + "tslib": "~2.6.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=16" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "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-inspector/diff-command": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-inspector/diff-command/-/diff-command-5.0.6.tgz", - "integrity": "sha512-DmFfOM5QHQphBvPyyRNmei1IY9DvoqySd/D3iV7/1iOBnFxNkljM8jqbUGe+jZfcrDdM9DRYJseb5d0atSXBRQ==", + "node_modules/@graphql-codegen/core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-4.0.2.tgz", + "integrity": "sha512-IZbpkhwVqgizcjNiaVzNAzm/xbWT6YnGgeOLwVjm4KbJn3V2jchVtuzHH09G5/WkkLSk2wgbXNdwjM41JxO6Eg==", + "dev": true, "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/core": "6.1.0", - "@graphql-inspector/logger": "5.0.1", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.0.0", + "tslib": "~2.6.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/docs-command": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@graphql-inspector/docs-command/-/docs-command-5.0.4.tgz", - "integrity": "sha512-NTQRWYzGNJy4Bnd+0NHNjOdgaETEUG112W+Nei/tPCRTs0Vi/UiW+UkGsQ3KxJozEkwgN8od39bVWohGTOPcpA==", + "node_modules/@graphql-codegen/gql-tag-operations": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.11.tgz", + "integrity": "sha512-EUQuBsYB5RtNlzBb/O0nJvbWC8HvPRWwVTHRf0ElOoQlJfRgfDom2GWmEM5hXa2afzMqB7AWxOH24ibOqiYnMQ==", + "dev": true, "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "open": "8.4.2", - "tslib": "2.6.2" + "@graphql-codegen/plugin-helpers": "^5.1.0", + "@graphql-codegen/visitor-plugin-common": "5.5.0", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/git-loader": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@graphql-inspector/git-loader/-/git-loader-5.0.1.tgz", - "integrity": "sha512-eZFNU/y1z4sZ9Axu8mB/J7mW+e78JnWgXG2vcT1TT2E1uzFm0x2oNONM2lgLCZGEJuwQDEnreok5CoHumIdE4Q==", + "node_modules/@graphql-codegen/plugin-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.1.0.tgz", + "integrity": "sha512-Y7cwEAkprbTKzVIe436TIw4w03jorsMruvCvu0HJkavaKMQbWY+lQ1RIuROgszDbxAyM35twB5/sUvYG5oW+yg==", + "dev": true, "dependencies": { - "@graphql-tools/git-loader": "8.0.6", - "tslib": "2.6.2" + "@graphql-tools/utils": "^10.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/github-loader": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@graphql-inspector/github-loader/-/github-loader-5.0.1.tgz", - "integrity": "sha512-CDsY4V1pEDzr5z5FlYTxcPa/7pKsuT/6xQmo1JghHQuYQPZ5TjtGsyNZwgQOjISMCw7pknXfifPBrFQKt6IOEA==", + "node_modules/@graphql-codegen/schema-ast": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-4.0.2.tgz", + "integrity": "sha512-5mVAOQQK3Oz7EtMl/l3vOQdc2aYClUzVDHHkMvZlunc+KlGgl81j8TLa+X7ANIllqU4fUEsQU3lJmk4hXP6K7Q==", + "dev": true, "dependencies": { - "@graphql-tools/github-loader": "8.0.1", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@graphql-codegen/plugin-helpers": "^5.0.3", + "@graphql-tools/utils": "^10.0.0", + "tslib": "~2.6.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/graphql-loader": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@graphql-inspector/graphql-loader/-/graphql-loader-5.0.1.tgz", - "integrity": "sha512-VZIcbkMhgak3sW4GehVIX/Qnwu1TmQidvaWs8YUiT+czPxKK1rqY/c/G3arwQDtqAdPMx8IwY1bT83ykfIyxfg==", + "node_modules/@graphql-codegen/typed-document-node": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.11.tgz", + "integrity": "sha512-btENKrSIUZ5UllS8sFhVZ+Y91VL0knK9gHxW/6/WzaCTxBQ+wOk07vQNeoWlvMrkl0QeUsGt6YvSo0SoPtsKdA==", + "dev": true, "dependencies": { - "@graphql-tools/graphql-file-loader": "8.0.1", - "tslib": "2.6.2" + "@graphql-codegen/plugin-helpers": "^5.1.0", + "@graphql-codegen/visitor-plugin-common": "5.5.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "tslib": "~2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/introspect-command": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-inspector/introspect-command/-/introspect-command-5.0.6.tgz", - "integrity": "sha512-qtYZLObzezNR5aS1qFeECwcK+MQGWDDywI3UzSKKtTLfoQJTAv3ECoo9PZxGFC2aEmZAKTEfkFICkbE9OCHu/w==", + "node_modules/@graphql-codegen/typescript": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.1.1.tgz", + "integrity": "sha512-+o5LOT71K9hdO4lDVnRGkkET5RdlKvxlQGug8dZgRGrhE2/xoPBsKfLhg9AoJGYMauNZxKj3blABQxHOKEku6Q==", + "dev": true, "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/core": "6.1.0", - "@graphql-inspector/logger": "5.0.1", - "tslib": "2.6.2" + "@graphql-codegen/plugin-helpers": "^5.1.0", + "@graphql-codegen/schema-ast": "^4.0.2", + "@graphql-codegen/visitor-plugin-common": "5.5.0", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-inspector/json-loader": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@graphql-inspector/json-loader/-/json-loader-5.0.1.tgz", - "integrity": "sha512-ql5zI2E/RNgLKDJ2HilTds2lUTv8ZXQfY5HG29iia85q/CIFslVTDbhzhbXRqmz4jsLd3KCi1LxpAeYQQMhCSQ==", + "node_modules/@graphql-codegen/typescript-operations": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.3.1.tgz", + "integrity": "sha512-yW5Iia6IK1VKiPm3oeukYMQN5pEBLwRlG8ZzQA9beeLQ8PskKyz6mjar6U7dJ2hc8pv/qT4R8kcJOQ2RloniAQ==", + "dev": true, "dependencies": { - "@graphql-tools/json-file-loader": "8.0.1", - "tslib": "2.6.2" + "@graphql-codegen/plugin-helpers": "^5.1.0", + "@graphql-codegen/typescript": "^4.1.1", + "@graphql-codegen/visitor-plugin-common": "5.5.0", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/loaders": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@graphql-inspector/loaders/-/loaders-4.0.5.tgz", - "integrity": "sha512-MQj82Pbo4YVgS1E3IjVvP3ByLQKQ6HHrjK+S21szXx46cKPxlc+MeKHpjfERSCmbdKAinP0MMHxVrmk7hyktow==", + "node_modules/@graphql-codegen/typescript-resolvers": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-4.2.1.tgz", + "integrity": "sha512-q/ggqNSKNGG9bn49DdZrw2KokagDZmzl1EpxIfzmpHrPa3XaCLfxQuNNEUhqEXtJzQZtLfuYvGy1y+MrTU8WnA==", + "dev": true, "dependencies": { - "@graphql-tools/code-file-loader": "8.1.2", - "@graphql-tools/load": "8.0.2", - "@graphql-tools/utils": "10.2.1", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@graphql-codegen/plugin-helpers": "^5.0.4", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/visitor-plugin-common": "5.3.1", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "tslib": "~2.6.0" }, "peerDependencies": { - "@graphql-inspector/config": "^4.0.0", - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/loaders/node_modules/@graphql-tools/utils": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", - "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", + "node_modules/@graphql-codegen/typescript-resolvers/node_modules/@graphql-codegen/visitor-plugin-common": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.1.tgz", + "integrity": "sha512-MktoBdNZhSmugiDjmFl1z6rEUUaqyxtFJYWnDilE7onkPgyw//O0M+TuPBJPBWdyV6J2ond0Hdqtq+rkghgSIQ==", + "dev": true, "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "cross-inspect": "1.0.0", - "dset": "^3.1.2", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" + "@graphql-codegen/plugin-helpers": "^5.0.4", + "@graphql-tools/optimize": "^2.0.0", + "@graphql-tools/relay-operation-optimizer": "^7.0.0", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "dependency-graph": "^0.11.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "~2.6.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "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-inspector/logger": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@graphql-inspector/logger/-/logger-5.0.1.tgz", - "integrity": "sha512-rEo+HoQt+qjdayy7p5vcR9GeGTdKXmN0LbIm3W+jKKoXeAMlV4zHxnOW6jEhO6E0eVQxf8Sc1TlcH78i2P2a9w==", + "node_modules/@graphql-codegen/visitor-plugin-common": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.5.0.tgz", + "integrity": "sha512-FSkxe/o4qKbpK+ipIT/jxZLYH0+3+XdIrJWsKlCW9wwJMF9mEJLJtzZNcxHSjz7+Eny6SUElAT2dqZ5XByxkog==", + "dev": true, "dependencies": { - "chalk": "4.1.2", - "figures": "3.2.0", - "log-symbols": "4.1.0", - "std-env": "3.7.0", - "tslib": "2.6.2" + "@graphql-codegen/plugin-helpers": "^5.1.0", + "@graphql-tools/optimize": "^2.0.0", + "@graphql-tools/relay-operation-optimizer": "^7.0.0", + "@graphql-tools/utils": "^10.0.0", + "auto-bind": "~4.0.0", + "change-case-all": "1.0.15", + "dependency-graph": "^0.11.0", + "graphql-tag": "^2.11.0", + "parse-filepath": "^1.0.2", + "tslib": "~2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16" + }, + "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-inspector/serve-command": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@graphql-inspector/serve-command/-/serve-command-5.0.5.tgz", - "integrity": "sha512-dWAg51LHrXoZb5NqE/G/nTaeJ2FrQJZc+mCz6l3fTBL6pU6szyMx4+Cxq+JmGCJVD71N4Fh+h9B4psDpnOFtBQ==", + "node_modules/@graphql-eslint/eslint-plugin": { + "version": "3.20.1", + "resolved": "https://registry.npmjs.org/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.20.1.tgz", + "integrity": "sha512-RbwVlz1gcYG62sECR1u0XqMh8w5e5XMCCZoMvPQ3nJzEBCTfXLGX727GBoRmSvY1x4gJmqNZ1lsOX7lZY14RIw==", + "dev": true, + "license": "MIT", "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/logger": "5.0.1", - "graphql-yoga": "5.3.1", - "open": "8.4.2", - "tslib": "2.6.2" + "@babel/code-frame": "^7.18.6", + "@graphql-tools/code-file-loader": "^7.3.6", + "@graphql-tools/graphql-tag-pluck": "^7.3.6", + "@graphql-tools/utils": "^9.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "graphql-config": "^4.4.0", + "graphql-depth-limit": "^1.1.0", + "lodash.lowercase": "^4.3.0", + "tslib": "^2.4.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=12" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "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-inspector/similar-command": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-inspector/similar-command/-/similar-command-5.0.6.tgz", - "integrity": "sha512-40SaZtxIXEZ0V/EkiG6N2in+PSeVoVcwmtl1ETbysGXZ6xC9Fu+Qi0lE7lbKWKjzw5nv9hYpznDJ1oIsBuN+hQ==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/batch-execute": { + "version": "8.5.22", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-8.5.22.tgz", + "integrity": "sha512-hcV1JaY6NJQFQEwCKrYhpfLK8frSXDbtNMoTur98u10Cmecy1zrqNKSqhEyGetpgHxaJRqszGzKeI3RuroDN6A==", + "dev": true, "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/core": "6.1.0", - "@graphql-inspector/logger": "5.0.1", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@graphql-tools/utils": "^9.2.1", + "dataloader": "^2.2.2", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-inspector/url-loader": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@graphql-inspector/url-loader/-/url-loader-5.0.1.tgz", - "integrity": "sha512-7OPJfTJgqptJyfsrpntsn3GEMpSZWxkJO+KaMIZfqDsiWN/zyvNqB0Amogi3d7xxtU1fnB3NCN5VWCFuiRSPXg==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/code-file-loader": { + "version": "7.3.23", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-7.3.23.tgz", + "integrity": "sha512-8Wt1rTtyTEs0p47uzsPJ1vAtfAx0jmxPifiNdmo9EOCuUPyQGEbMaik/YkqZ7QUFIEYEQu+Vgfo8tElwOPtx5Q==", + "dev": true, "dependencies": { - "@graphql-tools/url-loader": "8.0.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@graphql-tools/graphql-tag-pluck": "7.5.2", + "@graphql-tools/utils": "^9.2.1", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-inspector/validate-command": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@graphql-inspector/validate-command/-/validate-command-5.0.6.tgz", - "integrity": "sha512-SO4esOmsdUEueGA2kMjsoXSrvQrtZnJF7wKhcUwJrxIBm8aHf1V5wtorAk4ajIzhlD6a5Yd35GHI9hbjXnIZuQ==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/delegate": { + "version": "9.0.35", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-9.0.35.tgz", + "integrity": "sha512-jwPu8NJbzRRMqi4Vp/5QX1vIUeUPpWmlQpOkXQD2r1X45YsVceyUUBnktCrlJlDB4jPRVy7JQGwmYo3KFiOBMA==", + "dev": true, "dependencies": { - "@graphql-inspector/commands": "5.0.4", - "@graphql-inspector/core": "6.1.0", - "@graphql-inspector/logger": "5.0.1", - "@graphql-tools/utils": "10.2.1", - "tslib": "2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@graphql-tools/batch-execute": "^8.5.22", + "@graphql-tools/executor": "^0.0.20", + "@graphql-tools/schema": "^9.0.19", + "@graphql-tools/utils": "^9.2.1", + "dataloader": "^2.2.2", + "tslib": "^2.5.0", + "value-or-promise": "^1.0.12" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-inspector/validate-command/node_modules/@graphql-tools/utils": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", - "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-0.0.20.tgz", + "integrity": "sha512-GdvNc4vszmfeGvUqlcaH1FjBoguvMYzxAfT6tDd4/LgwymepHhinqLNA5otqwVLW+JETcDaK7xGENzFomuE6TA==", + "dev": true, "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "cross-inspect": "1.0.0", - "dset": "^3.1.2", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" + "@graphql-tools/utils": "^9.2.1", + "@graphql-typed-document-node/core": "3.2.0", + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/apollo-engine-loader": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.1.tgz", - "integrity": "sha512-NaPeVjtrfbPXcl+MLQCJLWtqe2/E4bbAqcauEOQ+3sizw1Fc2CNmhHRF8a6W4D0ekvTRRXAMptXYgA2uConbrA==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor-graphql-ws": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-0.0.14.tgz", + "integrity": "sha512-P2nlkAsPZKLIXImFhj0YTtny5NQVGSsKnhi7PzXiaHSXc6KkzqbWZHKvikD4PObanqg+7IO58rKFpGXP7eeO+w==", "dev": true, "dependencies": { - "@ardatan/sync-fetch": "^0.0.1", - "@graphql-tools/utils": "^10.0.13", - "@whatwg-node/fetch": "^0.9.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" + "@graphql-tools/utils": "^9.2.1", + "@repeaterjs/repeater": "3.0.4", + "@types/ws": "^8.0.0", + "graphql-ws": "5.12.1", + "isomorphic-ws": "5.0.0", + "tslib": "^2.4.0", + "ws": "8.13.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor-http": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-0.1.10.tgz", + "integrity": "sha512-hnAfbKv0/lb9s31LhWzawQ5hghBfHS+gYWtqxME6Rl0Aufq9GltiiLBcl7OVVOnkLF0KhwgbYP1mB5VKmgTGpg==", "dev": true, - "engines": { - "node": ">=16.0.0" + "dependencies": { + "@graphql-tools/utils": "^9.2.1", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/fetch": "^0.8.1", + "dset": "^3.1.2", + "extract-files": "^11.0.0", + "meros": "^1.2.1", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/executor-legacy-ws": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-0.0.11.tgz", + "integrity": "sha512-4ai+NnxlNfvIQ4c70hWFvOZlSUN8lt7yc+ZsrwtNFbFPH/EroIzFMapAxM9zwyv9bH38AdO3TQxZ5zNxgBdvUw==", "dev": true, "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", - "urlpattern-polyfill": "^10.0.0" + "@graphql-tools/utils": "^9.2.1", + "@types/ws": "^8.0.0", + "isomorphic-ws": "5.0.0", + "tslib": "^2.4.0", + "ws": "8.13.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/graphql-file-loader": { + "version": "7.5.17", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.5.17.tgz", + "integrity": "sha512-hVwwxPf41zOYgm4gdaZILCYnKB9Zap7Ys9OhY1hbwuAuC4MMNY9GpUjoTU3CQc3zUiPoYStyRtUGkHSJZ3HxBw==", "dev": true, "dependencies": { - "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", - "busboy": "^1.6.0", - "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "@graphql-tools/import": "6.7.18", + "@graphql-tools/utils": "^9.2.1", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/apollo-engine-loader/node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true - }, - "node_modules/@graphql-tools/batch-execute": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-9.0.4.tgz", - "integrity": "sha512-kkebDLXgDrep5Y0gK1RN3DMUlLqNhg60OAz0lTCqrYeja6DshxLtLkj+zV4mVbBA4mQOEoBmw6g1LZs3dA84/w==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/import": { + "version": "6.7.18", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-6.7.18.tgz", + "integrity": "sha512-XQDdyZTp+FYmT7as3xRWH/x8dx0QZA2WZqfMF5EWb36a0PiH7WwlRQYIdyYXj8YCLpiWkeBXgBRHmMnwEYR8iQ==", + "dev": true, "dependencies": { - "@graphql-tools/utils": "^10.0.13", - "dataloader": "^2.2.2", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" - }, - "engines": { - "node": ">=16.0.0" + "@graphql-tools/utils": "^9.2.1", + "resolve-from": "5.0.0", + "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/code-file-loader": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.2.tgz", - "integrity": "sha512-GrLzwl1QV2PT4X4TEEfuTmZYzIZHLqoTGBjczdUzSqgCCcqwWzLB3qrJxFQfI8e5s1qZ1bhpsO9NoMn7tvpmyA==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/json-file-loader": { + "version": "7.4.18", + "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-7.4.18.tgz", + "integrity": "sha512-AJ1b6Y1wiVgkwsxT5dELXhIVUPs/u3VZ8/0/oOtpcoyO/vAeM5rOvvWegzicOOnQw8G45fgBRMkkRfeuwVt6+w==", + "dev": true, "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.3.1", - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^9.2.1", "globby": "^11.0.3", "tslib": "^2.4.0", "unixify": "^1.0.0" }, - "engines": { - "node": ">=16.0.0" - }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/code-file-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.1.tgz", - "integrity": "sha512-ujits9tMqtWQQq4FI4+qnVPpJvSEn7ogKtyN/gfNT+ErIn6z1e4gyVGQpTK5sgAUXq1lW4gU/5fkFFC5/sL2rQ==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/load": { + "version": "7.8.14", + "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-7.8.14.tgz", + "integrity": "sha512-ASQvP+snHMYm+FhIaLxxFgVdRaM0vrN9wW2BKInQpktwWTXVyk+yP5nQUCEGmn0RTdlPKrffBaigxepkEAJPrg==", + "dev": true, "dependencies": { - "@babel/core": "^7.22.9", - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/schema": "^9.0.18", + "@graphql-tools/utils": "^9.2.1", + "p-limit": "3.1.0", "tslib": "^2.4.0" }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@graphql-tools/delegate": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.4.tgz", - "integrity": "sha512-WswZRbQZMh/ebhc8zSomK9DIh6Pd5KbuiMsyiKkKz37TWTrlCOe+4C/fyrBFez30ksq6oFyCeSKMwfrCbeGo0Q==", - "dependencies": { - "@graphql-tools/batch-execute": "^9.0.4", - "@graphql-tools/executor": "^1.2.1", - "@graphql-tools/schema": "^10.0.3", - "@graphql-tools/utils": "^10.0.13", - "dataloader": "^2.2.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=16.0.0" - }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/documents": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/documents/-/documents-1.0.0.tgz", - "integrity": "sha512-rHGjX1vg/nZ2DKqRGfDPNC55CWZBMldEVcH+91BThRa6JeT80NqXknffLLEZLRUxyikCfkwMsk6xR3UNMqG0Rg==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/merge": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", + "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", "dev": true, "dependencies": { - "lodash.sortby": "^4.7.0", + "@graphql-tools/utils": "^9.2.1", "tslib": "^2.4.0" }, - "engines": { - "node": ">=16.0.0" - }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/executor": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.6.tgz", - "integrity": "sha512-+1kjfqzM5T2R+dCw7F4vdJ3CqG+fY/LYJyhNiWEFtq0ToLwYzR/KKyD8YuzTirEjSxWTVlcBh7endkx5n5F6ew==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/schema": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", + "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", + "dev": true, "dependencies": { - "@graphql-tools/utils": "^10.1.1", - "@graphql-typed-document-node/core": "3.2.0", - "@repeaterjs/repeater": "^3.0.4", + "@graphql-tools/merge": "^8.4.1", + "@graphql-tools/utils": "^9.2.1", "tslib": "^2.4.0", "value-or-promise": "^1.0.12" }, - "engines": { - "node": ">=16.0.0" - }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/executor-graphql-ws": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.1.2.tgz", - "integrity": "sha512-+9ZK0rychTH1LUv4iZqJ4ESbmULJMTsv3XlFooPUngpxZkk00q6LqHKJRrsLErmQrVaC7cwQCaRBJa0teK17Lg==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/url-loader": { + "version": "7.17.18", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-7.17.18.tgz", + "integrity": "sha512-ear0CiyTj04jCVAxi7TvgbnGDIN2HgqzXzwsfcqiVg9cvjT40NcMlZ2P1lZDgqMkZ9oyLTV8Bw6j+SyG6A+xPw==", + "dev": true, "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@ardatan/sync-fetch": "^0.0.1", + "@graphql-tools/delegate": "^9.0.31", + "@graphql-tools/executor-graphql-ws": "^0.0.14", + "@graphql-tools/executor-http": "^0.1.7", + "@graphql-tools/executor-legacy-ws": "^0.0.11", + "@graphql-tools/utils": "^9.2.1", + "@graphql-tools/wrap": "^9.4.2", "@types/ws": "^8.0.0", - "graphql-ws": "^5.14.0", + "@whatwg-node/fetch": "^0.8.0", "isomorphic-ws": "^5.0.0", "tslib": "^2.4.0", - "ws": "^8.13.0" + "value-or-promise": "^1.0.11", + "ws": "^8.12.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dev": true, + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/executor-http": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.0.9.tgz", - "integrity": "sha512-+NXaZd2MWbbrWHqU4EhXcrDbogeiCDmEbrAN+rMn4Nu2okDjn2MTFDbTIab87oEubQCH4Te1wDkWPKrzXup7+Q==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@graphql-tools/wrap": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-9.4.2.tgz", + "integrity": "sha512-DFcd9r51lmcEKn0JW43CWkkI2D6T9XI1juW/Yo86i04v43O9w2/k4/nx2XTJv4Yv+iXwUw7Ok81PGltwGJSDSA==", + "dev": true, "dependencies": { - "@graphql-tools/utils": "^10.0.13", - "@repeaterjs/repeater": "^3.0.4", - "@whatwg-node/fetch": "^0.9.0", - "extract-files": "^11.0.0", - "meros": "^1.2.1", + "@graphql-tools/delegate": "^9.0.31", + "@graphql-tools/schema": "^9.0.18", + "@graphql-tools/utils": "^9.2.1", "tslib": "^2.4.0", "value-or-promise": "^1.0.12" }, - "engines": { - "node": ">=16.0.0" - }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "engines": { - "node": ">=16.0.0" + "node_modules/@graphql-eslint/eslint-plugin/node_modules/@repeaterjs/repeater": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz", + "integrity": "sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==", + "dev": true + }, + "node_modules/@graphql-eslint/eslint-plugin/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/@graphql-tools/executor-http/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/cosmiconfig": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz", + "integrity": "sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==", + "dev": true, "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", - "urlpattern-polyfill": "^10.0.0" + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=14" } }, - "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/graphql-config": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-4.5.0.tgz", + "integrity": "sha512-x6D0/cftpLUJ0Ch1e5sj1TZn6Wcxx4oMfmhaG9shM0DKajA9iR+j1z86GSTQ19fShbGvrSSvbIQsHku6aQ6BBw==", + "dev": true, "dependencies": { - "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", - "busboy": "^1.6.0", - "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "@graphql-tools/graphql-file-loader": "^7.3.7", + "@graphql-tools/json-file-loader": "^7.3.7", + "@graphql-tools/load": "^7.5.5", + "@graphql-tools/merge": "^8.2.6", + "@graphql-tools/url-loader": "^7.9.7", + "@graphql-tools/utils": "^9.0.0", + "cosmiconfig": "8.0.0", + "jiti": "1.17.1", + "minimatch": "4.2.3", + "string-env-interpolation": "1.0.1", + "tslib": "^2.4.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 10.0.0" + }, + "peerDependencies": { + "cosmiconfig-toml-loader": "^1.0.0", + "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "cosmiconfig-toml-loader": { + "optional": true + } } }, - "node_modules/@graphql-tools/executor-http/node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + "node_modules/@graphql-eslint/eslint-plugin/node_modules/graphql-ws": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.12.1.tgz", + "integrity": "sha512-umt4f5NnMK46ChM2coO36PTFhHouBrK9stWWBczERguwYrGnPNxJ9dimU6IyOBfOkC6Izhkg4H8+F51W/8CYDg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": ">=0.11 <=16" + } }, - "node_modules/@graphql-tools/executor-legacy-ws": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.0.6.tgz", - "integrity": "sha512-lDSxz9VyyquOrvSuCCnld3256Hmd+QI2lkmkEv7d4mdzkxkK4ddAWW1geQiWrQvWmdsmcnGGlZ7gDGbhEExwqg==", + "node_modules/@graphql-eslint/eslint-plugin/node_modules/jiti": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.17.1.tgz", + "integrity": "sha512-NZIITw8uZQFuzQimqjUxIrIcEdxYDFIe/0xYfIlVXTkiBjjyBEvgasj5bb0/cHtPRD/NziPbT312sFrkI5ALpw==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/@graphql-eslint/eslint-plugin/node_modules/minimatch": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.3.tgz", + "integrity": "sha512-lIUdtK5hdofgCTu3aT0sOaHsYR37viUuIc0rwnnDXImbwFRcumyLMeZaM0t0I/fgxS6s6JMfu0rLD1Wz9pv1ng==", + "dev": true, "dependencies": { - "@graphql-tools/utils": "^10.0.13", - "@types/ws": "^8.0.0", - "isomorphic-ws": "^5.0.0", - "tslib": "^2.4.0", - "ws": "^8.15.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16.0.0" + "node": ">=10" + } + }, + "node_modules/@graphql-eslint/eslint-plugin/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/@graphql-tools/git-loader": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.6.tgz", - "integrity": "sha512-FQFO4H5wHAmHVyuUQrjvPE8re3qJXt50TWHuzrK3dEaief7JosmlnkLMDMbMBwtwITz9u1Wpl6doPhT2GwKtlw==", + "node_modules/@graphql-inspector/audit-command": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@graphql-inspector/audit-command/-/audit-command-5.0.7.tgz", + "integrity": "sha512-/pRpUgOW+WHDmj1iszZxkpjZwgOZ0QWhk4hmlqxty9mXkAFl8K/+X4aolGpEbOrkfEl7xy4I+Fv7CJzzbsSj5A==", "dependencies": { - "@graphql-tools/graphql-tag-pluck": "8.3.1", - "@graphql-tools/utils": "^10.0.13", - "is-glob": "4.0.3", - "micromatch": "^4.0.4", - "tslib": "^2.4.0", - "unixify": "^1.0.0" + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.2.0", + "@graphql-inspector/logger": "5.0.1", + "@graphql-tools/utils": "10.2.1", + "cli-table3": "0.6.3", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/git-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.1.tgz", - "integrity": "sha512-ujits9tMqtWQQq4FI4+qnVPpJvSEn7ogKtyN/gfNT+ErIn6z1e4gyVGQpTK5sgAUXq1lW4gU/5fkFFC5/sL2rQ==", + "node_modules/@graphql-inspector/audit-command/node_modules/@graphql-tools/utils": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", "dependencies": { - "@babel/core": "^7.22.9", - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^10.0.13", + "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", + "dset": "^3.1.2", "tslib": "^2.4.0" }, "engines": { @@ -3539,359 +3871,402 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/github-loader": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.1.tgz", - "integrity": "sha512-W4dFLQJ5GtKGltvh/u1apWRFKBQOsDzFxO9cJkOYZj1VzHCpRF43uLST4VbCfWve+AwBqOuKr7YgkHoxpRMkcg==", + "node_modules/@graphql-inspector/cli": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@graphql-inspector/cli/-/cli-5.0.7.tgz", + "integrity": "sha512-kkUb2LK2Sprhl6vrLA7rzrCHrJ+HdOTbgS/G5FJZNHjKaU0P3wwAAdinGntY8JUXlkn5KewBNBeChtTG6Fterg==", "dependencies": { - "@ardatan/sync-fetch": "^0.0.1", - "@graphql-tools/executor-http": "^1.0.9", - "@graphql-tools/graphql-tag-pluck": "^8.0.0", - "@graphql-tools/utils": "^10.0.13", - "@whatwg-node/fetch": "^0.9.0", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" + "@babel/core": "7.24.6", + "@graphql-inspector/audit-command": "5.0.7", + "@graphql-inspector/code-loader": "5.0.1", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/config": "4.0.2", + "@graphql-inspector/coverage-command": "6.1.1", + "@graphql-inspector/diff-command": "5.0.7", + "@graphql-inspector/docs-command": "5.0.4", + "@graphql-inspector/git-loader": "5.0.1", + "@graphql-inspector/github-loader": "5.0.1", + "@graphql-inspector/graphql-loader": "5.0.1", + "@graphql-inspector/introspect-command": "5.0.7", + "@graphql-inspector/json-loader": "5.0.1", + "@graphql-inspector/loaders": "4.0.5", + "@graphql-inspector/serve-command": "5.0.6", + "@graphql-inspector/similar-command": "5.0.7", + "@graphql-inspector/url-loader": "5.0.1", + "@graphql-inspector/validate-command": "5.0.7", + "tslib": "2.6.2", + "yargs": "17.7.2" + }, + "bin": { + "graphql-inspector": "cjs/index.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/github-loader/node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.0.tgz", - "integrity": "sha512-gNqukC+s7iHC7vQZmx1SEJQmLnOguBq+aqE2zV2+o1hxkExvKqyFli1SY/9gmukFIKpKutCIj+8yLOM+jARutw==", + "node_modules/@graphql-inspector/code-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/code-loader/-/code-loader-5.0.1.tgz", + "integrity": "sha512-kdyP76g0QrtOFRda67+aNshSf0PXYyGJLiGxoVBogpAbkzDRhZQZAsdQVKP0tdEQAn4w0zN6VBdmpF/PAeBO5A==", "dependencies": { - "@babel/core": "^7.22.9", - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^10.0.13", - "tslib": "^2.4.0" + "@graphql-tools/code-file-loader": "8.1.2", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "node_modules/@graphql-inspector/commands": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@graphql-inspector/commands/-/commands-5.0.4.tgz", + "integrity": "sha512-m6SzYxjkKhor7pV33r1FSL2Wq/epzeWDE1cfPT/eFJ4qKavTBcglr+Vpien6PK1a2vy69GhviFhMoJEakrlZMA==", + "dependencies": { + "tslib": "2.6.2" + }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@graphql-inspector/config": "^4.0.0", + "@graphql-inspector/loaders": "^4.0.0", + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", + "yargs": "17.7.2" } }, - "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "node_modules/@graphql-inspector/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@graphql-inspector/config/-/config-4.0.2.tgz", + "integrity": "sha512-fnIwVpGM5AtTr4XyV8NJkDnwpXxZSBzi3BopjuXwBPXXD1F3tcVkCKNT6/5WgUQGfNPskBVbitcOPtM4hIYAOQ==", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", - "urlpattern-polyfill": "^10.0.0" + "tslib": "2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", + "node_modules/@graphql-inspector/core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@graphql-inspector/core/-/core-6.2.0.tgz", + "integrity": "sha512-pazUGHdMJQqEdxt+hXRyU/E2PUMvxjvTpjkTZdhuLiEgNn9RNZQjpvq2vqO1sifWujL+6C0mJH8zSnoldb5Jfg==", "dependencies": { - "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", - "busboy": "^1.6.0", - "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "dependency-graph": "1.0.0", + "object-inspect": "1.13.2", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/github-loader/node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + "node_modules/@graphql-inspector/core/node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "engines": { + "node": ">=4" + } }, - "node_modules/@graphql-tools/graphql-file-loader": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.1.tgz", - "integrity": "sha512-7gswMqWBabTSmqbaNyWSmRRpStWlcCkBc73E6NZNlh4YNuiyKOwbvSkOUYFOqFMfEL+cFsXgAvr87Vz4XrYSbA==", + "node_modules/@graphql-inspector/coverage-command": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/coverage-command/-/coverage-command-6.1.1.tgz", + "integrity": "sha512-DUumb17zTWXQeWmXFbi2QmTxuU1GdCk6dgL24NiX37qiWEpyQMj7YBaa8R0Dy9YADxpj7AyJasSOrefJ5g6lFw==", "dependencies": { - "@graphql-tools/import": "7.0.1", - "@graphql-tools/utils": "^10.0.13", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.2.0", + "@graphql-inspector/logger": "5.0.1", + "@graphql-tools/utils": "10.2.1", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/graphql-tag-pluck": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-7.5.2.tgz", - "integrity": "sha512-RW+H8FqOOLQw0BPXaahYepVSRjuOHw+7IL8Opaa5G5uYGOBxoXR7DceyQ7BcpMgktAOOmpDNQ2WtcboChOJSRA==", - "dev": true, + "node_modules/@graphql-inspector/coverage-command/node_modules/@graphql-tools/utils": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", "dependencies": { - "@babel/parser": "^7.16.8", - "@babel/plugin-syntax-import-assertions": "^7.20.0", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8", - "@graphql-tools/utils": "^9.2.1", + "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", + "dset": "^3.1.2", "tslib": "^2.4.0" }, + "engines": { + "node": ">=16.0.0" + }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/graphql-tag-pluck/node_modules/@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", - "dev": true, + "node_modules/@graphql-inspector/diff-command": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@graphql-inspector/diff-command/-/diff-command-5.0.7.tgz", + "integrity": "sha512-yhTgQIgQClkgmVkdvWMpDLWMwXcwTagZIt9KDE0QlJH1dL4XP/Q7mVlhnay5bJKMVVKPPqNIAlUNcP6lm8UODA==", "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "tslib": "^2.4.0" + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.2.0", + "@graphql-inspector/logger": "5.0.1", + "tslib": "2.6.2" + }, + "engines": { + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/import": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.1.tgz", - "integrity": "sha512-935uAjAS8UAeXThqHfYVr4HEAp6nHJ2sximZKO1RzUTq5WoALMAhhGARl0+ecm6X+cqNUwIChJbjtaa6P/ML0w==", + "node_modules/@graphql-inspector/docs-command": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@graphql-inspector/docs-command/-/docs-command-5.0.4.tgz", + "integrity": "sha512-NTQRWYzGNJy4Bnd+0NHNjOdgaETEUG112W+Nei/tPCRTs0Vi/UiW+UkGsQ3KxJozEkwgN8od39bVWohGTOPcpA==", "dependencies": { - "@graphql-tools/utils": "^10.0.13", - "resolve-from": "5.0.0", - "tslib": "^2.4.0" + "@graphql-inspector/commands": "5.0.4", + "open": "8.4.2", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/json-file-loader": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.1.tgz", - "integrity": "sha512-lAy2VqxDAHjVyqeJonCP6TUemrpYdDuKt25a10X6zY2Yn3iFYGnuIDQ64cv3ytyGY6KPyPB+Kp+ZfOkNDG3FQA==", + "node_modules/@graphql-inspector/git-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/git-loader/-/git-loader-5.0.1.tgz", + "integrity": "sha512-eZFNU/y1z4sZ9Axu8mB/J7mW+e78JnWgXG2vcT1TT2E1uzFm0x2oNONM2lgLCZGEJuwQDEnreok5CoHumIdE4Q==", "dependencies": { - "@graphql-tools/utils": "^10.0.13", - "globby": "^11.0.3", - "tslib": "^2.4.0", - "unixify": "^1.0.0" + "@graphql-tools/git-loader": "8.0.6", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/load": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.0.2.tgz", - "integrity": "sha512-S+E/cmyVmJ3CuCNfDuNF2EyovTwdWfQScXv/2gmvJOti2rGD8jTt9GYVzXaxhblLivQR9sBUCNZu/w7j7aXUCA==", + "node_modules/@graphql-inspector/github-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/github-loader/-/github-loader-5.0.1.tgz", + "integrity": "sha512-CDsY4V1pEDzr5z5FlYTxcPa/7pKsuT/6xQmo1JghHQuYQPZ5TjtGsyNZwgQOjISMCw7pknXfifPBrFQKt6IOEA==", "dependencies": { - "@graphql-tools/schema": "^10.0.3", - "@graphql-tools/utils": "^10.0.13", - "p-limit": "3.1.0", - "tslib": "^2.4.0" + "@graphql-tools/github-loader": "8.0.1", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/merge": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.6.tgz", - "integrity": "sha512-TmkzFTFVieHnqu9mPTF6RxAQltaprpDQnM5HMTPSyMLXnJGMTvdWejV0yORKj7DW1YSi791/sUnKf8HytepBFQ==", + "node_modules/@graphql-inspector/graphql-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/graphql-loader/-/graphql-loader-5.0.1.tgz", + "integrity": "sha512-VZIcbkMhgak3sW4GehVIX/Qnwu1TmQidvaWs8YUiT+czPxKK1rqY/c/G3arwQDtqAdPMx8IwY1bT83ykfIyxfg==", "dependencies": { - "@graphql-tools/utils": "^10.5.4", - "tslib": "^2.4.0" + "@graphql-tools/graphql-file-loader": "8.0.1", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/optimize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-2.0.0.tgz", - "integrity": "sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==", - "dev": true, + "node_modules/@graphql-inspector/introspect-command": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@graphql-inspector/introspect-command/-/introspect-command-5.0.7.tgz", + "integrity": "sha512-qWTtLfs25p+jjVz2uLV5358SXFs83lxDHfDuSeTC074MEATBH3v8BDyB1ai3m3rNlkojqf1AKnG8oc09P0Px0w==", "dependencies": { - "tslib": "^2.4.0" + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.2.0", + "@graphql-inspector/logger": "5.0.1", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/prisma-loader": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-8.0.3.tgz", - "integrity": "sha512-oZhxnMr3Jw2WAW1h9FIhF27xWzIB7bXWM8olz4W12oII4NiZl7VRkFw9IT50zME2Bqi9LGh9pkmMWkjvbOpl+Q==", - "dev": true, + "node_modules/@graphql-inspector/json-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/json-loader/-/json-loader-5.0.1.tgz", + "integrity": "sha512-ql5zI2E/RNgLKDJ2HilTds2lUTv8ZXQfY5HG29iia85q/CIFslVTDbhzhbXRqmz4jsLd3KCi1LxpAeYQQMhCSQ==", "dependencies": { - "@graphql-tools/url-loader": "^8.0.2", - "@graphql-tools/utils": "^10.0.13", - "@types/js-yaml": "^4.0.0", - "@types/json-stable-stringify": "^1.0.32", - "@whatwg-node/fetch": "^0.9.0", - "chalk": "^4.1.0", - "debug": "^4.3.1", - "dotenv": "^16.0.0", - "graphql-request": "^6.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "jose": "^5.0.0", - "js-yaml": "^4.0.0", - "json-stable-stringify": "^1.0.1", - "lodash": "^4.17.20", - "scuid": "^1.1.0", - "tslib": "^2.4.0", - "yaml-ast-parser": "^0.0.43" + "@graphql-tools/json-file-loader": "8.0.1", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "dev": true, + "node_modules/@graphql-inspector/loaders": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@graphql-inspector/loaders/-/loaders-4.0.5.tgz", + "integrity": "sha512-MQj82Pbo4YVgS1E3IjVvP3ByLQKQ6HHrjK+S21szXx46cKPxlc+MeKHpjfERSCmbdKAinP0MMHxVrmk7hyktow==", + "dependencies": { + "@graphql-tools/code-file-loader": "8.1.2", + "@graphql-tools/load": "8.0.2", + "@graphql-tools/utils": "10.2.1", + "tslib": "2.6.2" + }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@graphql-inspector/config": "^4.0.0", + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/fetch": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", - "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", - "dev": true, + "node_modules/@graphql-inspector/loaders/node_modules/@graphql-tools/utils": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", - "urlpattern-polyfill": "^10.0.0" + "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", + "dset": "^3.1.2", + "tslib": "^2.4.0" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", - "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", - "dev": true, + "node_modules/@graphql-inspector/logger": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/logger/-/logger-5.0.1.tgz", + "integrity": "sha512-rEo+HoQt+qjdayy7p5vcR9GeGTdKXmN0LbIm3W+jKKoXeAMlV4zHxnOW6jEhO6E0eVQxf8Sc1TlcH78i2P2a9w==", "dependencies": { - "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", - "busboy": "^1.6.0", - "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "chalk": "4.1.2", + "figures": "3.2.0", + "log-symbols": "4.1.0", + "std-env": "3.7.0", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@graphql-tools/prisma-loader/node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true - }, - "node_modules/@graphql-tools/relay-operation-optimizer": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.1.tgz", - "integrity": "sha512-y0ZrQ/iyqWZlsS/xrJfSir3TbVYJTYmMOu4TaSz6F4FRDTQ3ie43BlKkhf04rC28pnUOS4BO9pDcAo1D30l5+A==", - "dev": true, + "node_modules/@graphql-inspector/serve-command": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@graphql-inspector/serve-command/-/serve-command-5.0.6.tgz", + "integrity": "sha512-eP1NgLvNv/K90iilBM/hr6KFUAmL686ns7drTP2icEtxajkHEP1T3DVCrV+QmiN27H4Qa1YAvNwooOnJfo9gkg==", "dependencies": { - "@ardatan/relay-compiler": "12.0.0", - "@graphql-tools/utils": "^10.0.13", - "tslib": "^2.4.0" + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/logger": "5.0.1", + "graphql-yoga": "5.7.0", + "open": "8.4.2", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/resolvers-composition": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/resolvers-composition/-/resolvers-composition-7.0.1.tgz", - "integrity": "sha512-EXcTi4OuGsj8UvQEleKVL4uJeOWc4MgOPK12qBCygkDMZArvBEzCa2IQ5Q9jWyCLcsNQxMXWyOm1rcQTrkJ+/w==", + "node_modules/@graphql-inspector/similar-command": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@graphql-inspector/similar-command/-/similar-command-5.0.7.tgz", + "integrity": "sha512-xIHl6wQEjeWb5p2KttDabSxtw7I8QcCIZkRCqtqDi+8hPvL36KZsZyLNfEEeeRejigxfw19D1N9NTAmk5W66jA==", "dependencies": { - "@graphql-tools/utils": "^10.0.13", - "lodash": "4.17.21", - "micromatch": "^4.0.4", - "tslib": "^2.4.0" + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.2.0", + "@graphql-inspector/logger": "5.0.1", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/schema": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.6.tgz", - "integrity": "sha512-EIJgPRGzpvDFEjVp+RF1zNNYIC36BYuIeZ514jFoJnI6IdxyVyIRDLx/ykgMdaa1pKQerpfdqDnsF4JnZoDHSQ==", + "node_modules/@graphql-inspector/url-loader": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-inspector/url-loader/-/url-loader-5.0.1.tgz", + "integrity": "sha512-7OPJfTJgqptJyfsrpntsn3GEMpSZWxkJO+KaMIZfqDsiWN/zyvNqB0Amogi3d7xxtU1fnB3NCN5VWCFuiRSPXg==", "dependencies": { - "@graphql-tools/merge": "^9.0.6", - "@graphql-tools/utils": "^10.5.4", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" + "@graphql-tools/url-loader": "8.0.2", + "tslib": "2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@graphql-tools/url-loader": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.2.tgz", - "integrity": "sha512-1dKp2K8UuFn7DFo1qX5c1cyazQv2h2ICwA9esHblEqCYrgf69Nk8N7SODmsfWg94OEaI74IqMoM12t7eIGwFzQ==", + "node_modules/@graphql-inspector/validate-command": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@graphql-inspector/validate-command/-/validate-command-5.0.7.tgz", + "integrity": "sha512-+cHfdI+HNMGsbDI5RLMMBWF+LeWXIkkCw2DA8ReqeZorPw6SBQNFOtcjP8U26Mau22e5PU2dTAMSwbf1nd9R1Q==", "dependencies": { - "@ardatan/sync-fetch": "^0.0.1", - "@graphql-tools/delegate": "^10.0.4", - "@graphql-tools/executor-graphql-ws": "^1.1.2", - "@graphql-tools/executor-http": "^1.0.9", - "@graphql-tools/executor-legacy-ws": "^1.0.6", + "@graphql-inspector/commands": "5.0.4", + "@graphql-inspector/core": "6.2.0", + "@graphql-inspector/logger": "5.0.1", + "@graphql-tools/utils": "10.2.1", + "tslib": "2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-inspector/validate-command/node_modules/@graphql-tools/utils": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.1.tgz", + "integrity": "sha512-U8OMdkkEt3Vp3uYHU2pMc6mwId7axVAcSSmcqJcUmWNPqY2pfee5O655ybTI2kNPWAe58Zu6gLu4Oi4QT4BgWA==", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.0", + "dset": "^3.1.2", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/apollo-engine-loader": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.1.tgz", + "integrity": "sha512-NaPeVjtrfbPXcl+MLQCJLWtqe2/E4bbAqcauEOQ+3sizw1Fc2CNmhHRF8a6W4D0ekvTRRXAMptXYgA2uConbrA==", + "dev": true, + "dependencies": { + "@ardatan/sync-fetch": "^0.0.1", "@graphql-tools/utils": "^10.0.13", - "@graphql-tools/wrap": "^10.0.2", - "@types/ws": "^8.0.0", "@whatwg-node/fetch": "^0.9.0", - "isomorphic-ws": "^5.0.0", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.11", - "ws": "^8.12.0" + "tslib": "^2.4.0" }, "engines": { "node": ">=16.0.0" @@ -3900,18 +4275,20 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/events": { + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/events": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "dev": true, "engines": { "node": ">=16.0.0" } }, - "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/fetch": { + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/fetch": { "version": "0.9.17", "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", + "dev": true, "dependencies": { "@whatwg-node/node-fetch": "^0.5.7", "urlpattern-polyfill": "^10.0.0" @@ -3920,10 +4297,11 @@ "node": ">=16.0.0" } }, - "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/node-fetch": { + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/node-fetch": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", + "dev": true, "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "@whatwg-node/events": "^0.1.0", @@ -3935,20 +4313,21 @@ "node": ">=16.0.0" } }, - "node_modules/@graphql-tools/url-loader/node_modules/urlpattern-polyfill": { + "node_modules/@graphql-tools/apollo-engine-loader/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true }, - "node_modules/@graphql-tools/utils": { - "version": "10.5.4", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.4.tgz", - "integrity": "sha512-XHnyCWSlg1ccsD8s0y6ugo5GZ5TpkTiFVNPSYms5G0s6Z/xTuSmiLBfeqgkfaCwLmLaQnRCmNDL2JRnqc2R5bQ==", + "node_modules/@graphql-tools/batch-execute": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-9.0.4.tgz", + "integrity": "sha512-kkebDLXgDrep5Y0gK1RN3DMUlLqNhg60OAz0lTCqrYeja6DshxLtLkj+zV4mVbBA4mQOEoBmw6g1LZs3dA84/w==", "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "cross-inspect": "1.0.1", - "dset": "^3.1.2", - "tslib": "^2.4.0" + "@graphql-tools/utils": "^10.0.13", + "dataloader": "^2.2.2", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" }, "engines": { "node": ">=16.0.0" @@ -3957,27 +4336,55 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/utils/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==", + "node_modules/@graphql-tools/code-file-loader": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.2.tgz", + "integrity": "sha512-GrLzwl1QV2PT4X4TEEfuTmZYzIZHLqoTGBjczdUzSqgCCcqwWzLB3qrJxFQfI8e5s1qZ1bhpsO9NoMn7tvpmyA==", + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.3.1", + "@graphql-tools/utils": "^10.0.13", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/code-file-loader/node_modules/@graphql-tools/graphql-tag-pluck": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.1.tgz", + "integrity": "sha512-ujits9tMqtWQQq4FI4+qnVPpJvSEn7ogKtyN/gfNT+ErIn6z1e4gyVGQpTK5sgAUXq1lW4gU/5fkFFC5/sL2rQ==", "dependencies": { + "@babel/core": "^7.22.9", + "@babel/parser": "^7.16.8", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8", + "@graphql-tools/utils": "^10.0.13", "tslib": "^2.4.0" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/wrap": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.0.5.tgz", - "integrity": "sha512-Cbr5aYjr3HkwdPvetZp1cpDWTGdD1Owgsb3z/ClzhmrboiK86EnQDxDvOJiQkDCPWE9lNBwj8Y4HfxroY0D9DQ==", + "node_modules/@graphql-tools/delegate": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.4.tgz", + "integrity": "sha512-WswZRbQZMh/ebhc8zSomK9DIh6Pd5KbuiMsyiKkKz37TWTrlCOe+4C/fyrBFez30ksq6oFyCeSKMwfrCbeGo0Q==", "dependencies": { - "@graphql-tools/delegate": "^10.0.4", + "@graphql-tools/batch-execute": "^9.0.4", + "@graphql-tools/executor": "^1.2.1", "@graphql-tools/schema": "^10.0.3", - "@graphql-tools/utils": "^10.1.1", - "tslib": "^2.4.0", - "value-or-promise": "^1.0.12" + "@graphql-tools/utils": "^10.0.13", + "dataloader": "^2.2.2", + "tslib": "^2.5.0" }, "engines": { "node": ">=16.0.0" @@ -3986,40 +4393,80 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-typed-document-node/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", - "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "node_modules/@graphql-tools/documents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/documents/-/documents-1.0.1.tgz", + "integrity": "sha512-aweoMH15wNJ8g7b2r4C4WRuJxZ0ca8HtNO54rkye/3duxTkW4fGBEutCx03jCIr5+a1l+4vFJNP859QnAVBVCA==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, "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" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-yoga/logger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-2.0.0.tgz", - "integrity": "sha512-Mg8psdkAp+YTG1OGmvU+xa6xpsAmSir0hhr3yFYPyLNwzUj95DdIwsMpKadDj9xDpYgJcH3Hp/4JMal9DhQimA==", + "node_modules/@graphql-tools/executor": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.3.3.tgz", + "integrity": "sha512-lpkgokF6vjGHNluANOTsWoSM0vuvUuVpjY1810tvM6Vlyoq2tJ+nmqweGbMsq/GLhmZQP1lY/nOkj2zPJXLWiw==", "dependencies": { - "tslib": "^2.5.2" + "@graphql-tools/utils": "^10.5.6", + "@graphql-typed-document-node/core": "3.2.0", + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-yoga/subscription": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-5.0.0.tgz", - "integrity": "sha512-Ri7sK8hmxd/kwaEa0YT8uqQUb2wOLsmBMxI90QDyf96lzOMJRgBuNYoEkU1pSgsgmW2glceZ96sRYfaXqwVxUw==", + "node_modules/@graphql-tools/executor-graphql-ws": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.1.2.tgz", + "integrity": "sha512-+9ZK0rychTH1LUv4iZqJ4ESbmULJMTsv3XlFooPUngpxZkk00q6LqHKJRrsLErmQrVaC7cwQCaRBJa0teK17Lg==", "dependencies": { - "@graphql-yoga/typed-event-target": "^3.0.0", + "@graphql-tools/utils": "^10.0.13", + "@types/ws": "^8.0.0", + "graphql-ws": "^5.14.0", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.4.0", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/executor-http": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.0.9.tgz", + "integrity": "sha512-+NXaZd2MWbbrWHqU4EhXcrDbogeiCDmEbrAN+rMn4Nu2okDjn2MTFDbTIab87oEubQCH4Te1wDkWPKrzXup7+Q==", + "dependencies": { + "@graphql-tools/utils": "^10.0.13", "@repeaterjs/repeater": "^3.0.4", - "@whatwg-node/events": "^0.1.0", - "tslib": "^2.5.2" + "@whatwg-node/fetch": "^0.9.0", + "extract-files": "^11.0.0", + "meros": "^1.2.1", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-yoga/subscription/node_modules/@whatwg-node/events": { + "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/events": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", @@ -4027,1502 +4474,2565 @@ "node": ">=16.0.0" } }, - "node_modules/@graphql-yoga/typed-event-target": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-3.0.0.tgz", - "integrity": "sha512-w+liuBySifrstuHbFrHoHAEyVnDFVib+073q8AeAJ/qqJfvFvAwUPLLtNohR/WDVRgSasfXtl3dcNuVJWN+rjg==", + "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/fetch": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", + "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", "dependencies": { - "@repeaterjs/repeater": "^3.0.4", - "tslib": "^2.5.2" + "@whatwg-node/node-fetch": "^0.5.7", + "urlpattern-polyfill": "^10.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/node-fetch": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", + "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@kamilkisiela/fast-url-parser": "^1.1.4", + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "tslib": "^2.3.1" }, "engines": { - "node": ">=10.10.0" + "node": ">=16.0.0" } }, - "node_modules/@humanwhocodes/config-array/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==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "node_modules/@graphql-tools/executor-http/node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@graphql-tools/executor-legacy-ws": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.0.6.tgz", + "integrity": "sha512-lDSxz9VyyquOrvSuCCnld3256Hmd+QI2lkmkEv7d4mdzkxkK4ddAWW1geQiWrQvWmdsmcnGGlZ7gDGbhEExwqg==", "dependencies": { - "brace-expansion": "^1.1.7" + "@graphql-tools/utils": "^10.0.13", + "@types/ws": "^8.0.0", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.4.0", + "ws": "^8.15.0" }, "engines": { - "node": "*" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "engines": { - "node": ">=12.22" + "node_modules/@graphql-tools/git-loader": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.6.tgz", + "integrity": "sha512-FQFO4H5wHAmHVyuUQrjvPE8re3qJXt50TWHuzrK3dEaief7JosmlnkLMDMbMBwtwITz9u1Wpl6doPhT2GwKtlw==", + "dependencies": { + "@graphql-tools/graphql-tag-pluck": "8.3.1", + "@graphql-tools/utils": "^10.0.13", + "is-glob": "4.0.3", + "micromatch": "^4.0.4", + "tslib": "^2.4.0", + "unixify": "^1.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==" - }, - "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" - }, - "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, + "node_modules/@graphql-tools/git-loader/node_modules/@graphql-tools/graphql-tag-pluck": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.1.tgz", + "integrity": "sha512-ujits9tMqtWQQq4FI4+qnVPpJvSEn7ogKtyN/gfNT+ErIn6z1e4gyVGQpTK5sgAUXq1lW4gU/5fkFFC5/sL2rQ==", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@babel/core": "^7.22.9", + "@babel/parser": "^7.16.8", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8", + "@graphql-tools/utils": "^10.0.13", + "tslib": "^2.4.0" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, + "node_modules/@graphql-tools/github-loader": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.1.tgz", + "integrity": "sha512-W4dFLQJ5GtKGltvh/u1apWRFKBQOsDzFxO9cJkOYZj1VzHCpRF43uLST4VbCfWve+AwBqOuKr7YgkHoxpRMkcg==", + "dependencies": { + "@ardatan/sync-fetch": "^0.0.1", + "@graphql-tools/executor-http": "^1.0.9", + "@graphql-tools/graphql-tag-pluck": "^8.0.0", + "@graphql-tools/utils": "^10.0.13", + "@whatwg-node/fetch": "^0.9.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, "engines": { - "node": ">=12" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, + "node_modules/@graphql-tools/github-loader/node_modules/@graphql-tools/graphql-tag-pluck": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.0.tgz", + "integrity": "sha512-gNqukC+s7iHC7vQZmx1SEJQmLnOguBq+aqE2zV2+o1hxkExvKqyFli1SY/9gmukFIKpKutCIj+8yLOM+jARutw==", + "dependencies": { + "@babel/core": "^7.22.9", + "@babel/parser": "^7.16.8", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8", + "@graphql-tools/utils": "^10.0.13", + "tslib": "^2.4.0" + }, "engines": { - "node": ">=12" + "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/@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/@graphql-tools/github-loader/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "engines": { + "node": ">=16.0.0" + } }, - "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, + "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/fetch": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", + "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@whatwg-node/node-fetch": "^0.5.7", + "urlpattern-polyfill": "^10.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, + "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/node-fetch": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", + "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", "dependencies": { - "ansi-regex": "^6.0.1" + "@kamilkisiela/fast-url-parser": "^1.1.4", + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "tslib": "^2.3.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=16.0.0" } }, - "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, + "node_modules/@graphql-tools/github-loader/node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/@graphql-tools/graphql-file-loader": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.1.tgz", + "integrity": "sha512-7gswMqWBabTSmqbaNyWSmRRpStWlcCkBc73E6NZNlh4YNuiyKOwbvSkOUYFOqFMfEL+cFsXgAvr87Vz4XrYSbA==", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@graphql-tools/import": "7.0.1", + "@graphql-tools/utils": "^10.0.13", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "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/@graphql-tools/graphql-tag-pluck": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-7.5.2.tgz", + "integrity": "sha512-RW+H8FqOOLQw0BPXaahYepVSRjuOHw+7IL8Opaa5G5uYGOBxoXR7DceyQ7BcpMgktAOOmpDNQ2WtcboChOJSRA==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "@babel/parser": "^7.16.8", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8", + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@graphql-tools/graphql-tag-pluck/node_modules/@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@graphql-tools/import": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.1.tgz", + "integrity": "sha512-935uAjAS8UAeXThqHfYVr4HEAp6nHJ2sximZKO1RzUTq5WoALMAhhGARl0+ecm6X+cqNUwIChJbjtaa6P/ML0w==", + "dependencies": { + "@graphql-tools/utils": "^10.0.13", + "resolve-from": "5.0.0", + "tslib": "^2.4.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.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==", + "node_modules/@graphql-tools/json-file-loader": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.1.tgz", + "integrity": "sha512-lAy2VqxDAHjVyqeJonCP6TUemrpYdDuKt25a10X6zY2Yn3iFYGnuIDQ64cv3ytyGY6KPyPB+Kp+ZfOkNDG3FQA==", + "dependencies": { + "@graphql-tools/utils": "^10.0.13", + "globby": "^11.0.3", + "tslib": "^2.4.0", + "unixify": "^1.0.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@graphql-tools/load": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.0.2.tgz", + "integrity": "sha512-S+E/cmyVmJ3CuCNfDuNF2EyovTwdWfQScXv/2gmvJOti2rGD8jTt9GYVzXaxhblLivQR9sBUCNZu/w7j7aXUCA==", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@graphql-tools/schema": "^10.0.3", + "@graphql-tools/utils": "^10.0.13", + "p-limit": "3.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@kamilkisiela/fast-url-parser": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", - "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==" - }, - "node_modules/@messageformat/core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.3.0.tgz", - "integrity": "sha512-YcXd3remTDdeMxAlbvW6oV9d/01/DZ8DHUFwSttO3LMzIZj3iO0NRw+u1xlsNNORFI+u0EQzD52ZX3+Udi0T3g==", + "node_modules/@graphql-tools/merge": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.6.tgz", + "integrity": "sha512-TmkzFTFVieHnqu9mPTF6RxAQltaprpDQnM5HMTPSyMLXnJGMTvdWejV0yORKj7DW1YSi791/sUnKf8HytepBFQ==", "dependencies": { - "@messageformat/date-skeleton": "^1.0.0", - "@messageformat/number-skeleton": "^1.0.0", - "@messageformat/parser": "^5.1.0", - "@messageformat/runtime": "^3.0.1", - "make-plural": "^7.0.0", - "safe-identifier": "^0.4.1" + "@graphql-tools/utils": "^10.5.4", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@messageformat/date-skeleton": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.0.1.tgz", - "integrity": "sha512-jPXy8fg+WMPIgmGjxSlnGJn68h/2InfT0TNSkVx0IGXgp4ynnvYkbZ51dGWmGySEK+pBiYUttbQdu5XEqX5CRg==" - }, - "node_modules/@messageformat/number-skeleton": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz", - "integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==" - }, - "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==", + "node_modules/@graphql-tools/optimize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-2.0.0.tgz", + "integrity": "sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==", + "dev": true, "dependencies": { - "moo": "^0.5.1" + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@messageformat/runtime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz", - "integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==", + "node_modules/@graphql-tools/prisma-loader": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-8.0.3.tgz", + "integrity": "sha512-oZhxnMr3Jw2WAW1h9FIhF27xWzIB7bXWM8olz4W12oII4NiZl7VRkFw9IT50zME2Bqi9LGh9pkmMWkjvbOpl+Q==", + "dev": true, "dependencies": { - "make-plural": "^7.0.0" + "@graphql-tools/url-loader": "^8.0.2", + "@graphql-tools/utils": "^10.0.13", + "@types/js-yaml": "^4.0.0", + "@types/json-stable-stringify": "^1.0.32", + "@whatwg-node/fetch": "^0.9.0", + "chalk": "^4.1.0", + "debug": "^4.3.1", + "dotenv": "^16.0.0", + "graphql-request": "^6.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "jose": "^5.0.0", + "js-yaml": "^4.0.0", + "json-stable-stringify": "^1.0.1", + "lodash": "^4.17.20", + "scuid": "^1.1.0", + "tslib": "^2.4.0", + "yaml-ast-parser": "^0.0.43" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@microsoft/tsdoc": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", - "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", - "dev": true + "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@microsoft/tsdoc-config": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz", - "integrity": "sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==", + "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/fetch": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", + "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", "dev": true, "dependencies": { - "@microsoft/tsdoc": "0.15.0", - "ajv": "~8.12.0", - "jju": "~1.4.0", - "resolve": "~1.22.2" + "@whatwg-node/node-fetch": "^0.5.7", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/node-fetch": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", + "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", "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" + "@kamilkisiela/fast-url-parser": "^1.1.4", + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "tslib": "^2.3.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@microsoft/tsdoc-config/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==", + "node_modules/@graphql-tools/prisma-loader/node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", "dev": true }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", - "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", + "node_modules/@graphql-tools/relay-operation-optimizer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.1.tgz", + "integrity": "sha512-y0ZrQ/iyqWZlsS/xrJfSir3TbVYJTYmMOu4TaSz6F4FRDTQ3ie43BlKkhf04rC28pnUOS4BO9pDcAo1D30l5+A==", + "dev": true, "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@mui/base": { - "version": "5.0.0-alpha.112", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.112.tgz", - "integrity": "sha512-KPwb1iYPXsV/P8uu0SNQrj7v7YU6wdN4Eccc2lZQyRDW+f6PJYjHBuFUTYKc408B98Jvs1XbC/z5MN45a2DWrQ==", - "dependencies": { - "@babel/runtime": "^7.20.7", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.2", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "@ardatan/relay-compiler": "12.0.0", + "@graphql-tools/utils": "^10.0.13", + "tslib": "^2.4.0" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" + "node": ">=16.0.0" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", - "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@mui/icons-material": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.0.tgz", - "integrity": "sha512-I2LaOKqO8a0xcLGtIozC9xoXjZAto5G5gh0FYUMAlbsIHNHIjn4Xrw9rvjY20vZonyiGrZNMAlAXYkY6JvhF6A==", + "node_modules/@graphql-tools/resolvers-composition": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/resolvers-composition/-/resolvers-composition-7.0.2.tgz", + "integrity": "sha512-ipTV1xjJSSx8iI0AQ5beZfUSlV08R8B+FGsbBnGCKiZ1Li4Y+egxR7wOcI9F3TlQlMExdSq3CjtoVfPpeYWsPw==", "dependencies": { - "@babel/runtime": "^7.20.6" + "@graphql-tools/utils": "^10.5.5", + "lodash": "4.17.21", + "micromatch": "^4.0.8", + "tslib": "^2.4.0" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" + "node": ">=16.0.0" }, "peerDependencies": { - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@mui/lab": { - "version": "5.0.0-alpha.114", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.114.tgz", - "integrity": "sha512-tChDoLaJ3qcYk37GIwBL1KrCiW0gpmEY//D5z5nHWnO/mzx3axjRJZpBOBeGEvhuoO/Y3QzMz4rhTvqbGNkW0w==", - "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/base": "5.0.0-alpha.112", - "@mui/system": "^5.11.2", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.2", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "node_modules/@graphql-tools/schema": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.6.tgz", + "integrity": "sha512-EIJgPRGzpvDFEjVp+RF1zNNYIC36BYuIeZ514jFoJnI6IdxyVyIRDLx/ykgMdaa1pKQerpfdqDnsF4JnZoDHSQ==", + "dependencies": { + "@graphql-tools/merge": "^9.0.6", + "@graphql-tools/utils": "^10.5.4", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" + "node": ">=16.0.0" }, "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@mui/material": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.2.tgz", - "integrity": "sha512-PeraRDsghnDLzejorfe9ps1syxlB8UrGs+UKwg9GGlndv5Tghm+9nwuibrP2TCDC14mlryF+u2WlAOYaPPMwGA==", - "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/base": "5.0.0-alpha.112", - "@mui/core-downloads-tracker": "^5.11.2", - "@mui/system": "^5.11.2", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.2", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" + "node_modules/@graphql-tools/url-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.2.tgz", + "integrity": "sha512-1dKp2K8UuFn7DFo1qX5c1cyazQv2h2ICwA9esHblEqCYrgf69Nk8N7SODmsfWg94OEaI74IqMoM12t7eIGwFzQ==", + "dependencies": { + "@ardatan/sync-fetch": "^0.0.1", + "@graphql-tools/delegate": "^10.0.4", + "@graphql-tools/executor-graphql-ws": "^1.1.2", + "@graphql-tools/executor-http": "^1.0.9", + "@graphql-tools/executor-legacy-ws": "^1.0.6", + "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/wrap": "^10.0.2", + "@types/ws": "^8.0.0", + "@whatwg-node/fetch": "^0.9.0", + "isomorphic-ws": "^5.0.0", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.11", + "ws": "^8.12.0" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" + "node": ">=16.0.0" }, "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@mui/private-theming": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", - "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", + "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/events": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", + "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/fetch": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.17.tgz", + "integrity": "sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.14", - "prop-types": "^15.8.1" + "@whatwg-node/node-fetch": "^0.5.7", + "urlpattern-polyfill": "^10.0.0" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "node": ">=16.0.0" } }, - "node_modules/@mui/styled-engine": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", - "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", + "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/node-fetch": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.10.tgz", + "integrity": "sha512-KIAHepie/T1PRkUfze4t+bPlyvpxlWiXTPtcGlbIZ0vWkBJMdRmCg4ZrJ2y4XaO1eTPo1HlWYUuj1WvoIpumqg==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" + "@kamilkisiela/fast-url-parser": "^1.1.4", + "@whatwg-node/events": "^0.1.0", + "busboy": "^1.6.0", + "fast-querystring": "^1.1.1", + "tslib": "^2.3.1" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } + "node": ">=16.0.0" } }, - "node_modules/@mui/system": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", - "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", + "node_modules/@graphql-tools/url-loader/node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/@graphql-tools/utils": { + "version": "10.5.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.6.tgz", + "integrity": "sha512-JAC44rhbLzXUHiltceyEpWkxmX4e45Dfg19wRFoA9EbDxQVbOzVNF76eEECdg0J1owFsJwfLqCwz7/6xzrovOw==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.14", - "@mui/styled-engine": "^5.15.14", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" + "@graphql-typed-document-node/core": "^3.1.1", + "cross-inspect": "1.0.1", + "dset": "^3.1.2", + "tslib": "^2.4.0" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" + "node": ">=16.0.0" }, "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils/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==", + "dependencies": { + "tslib": "^2.4.0" }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@mui/system/node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "node_modules/@graphql-tools/wrap": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.0.5.tgz", + "integrity": "sha512-Cbr5aYjr3HkwdPvetZp1cpDWTGdD1Owgsb3z/ClzhmrboiK86EnQDxDvOJiQkDCPWE9lNBwj8Y4HfxroY0D9DQ==", + "dependencies": { + "@graphql-tools/delegate": "^10.0.4", + "@graphql-tools/schema": "^10.0.3", + "@graphql-tools/utils": "^10.1.1", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, "engines": { - "node": ">=6" + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@mui/types": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", - "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", + "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==", "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "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/@mui/utils": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", - "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", + "node_modules/@graphql-yoga/logger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-2.0.0.tgz", + "integrity": "sha512-Mg8psdkAp+YTG1OGmvU+xa6xpsAmSir0hhr3yFYPyLNwzUj95DdIwsMpKadDj9xDpYgJcH3Hp/4JMal9DhQimA==", "dependencies": { - "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "tslib": "^2.5.2" }, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "node": ">=18.0.0" } }, - "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/@graphql-yoga/subscription": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-5.0.1.tgz", + "integrity": "sha512-1wCB1DfAnaLzS+IdoOzELGGnx1ODEg9nzQXFh4u2j02vAnne6d+v4A7HIH9EqzVdPLoAaMKXCZUUdKs+j3z1fg==", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@graphql-yoga/typed-event-target": "^3.0.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/events": "^0.1.0", + "tslib": "^2.5.2" }, "engines": { - "node": ">= 8" + "node": ">=18.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/@graphql-yoga/subscription/node_modules/@whatwg-node/events": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.2.tgz", + "integrity": "sha512-ApcWxkrs1WmEMS2CaLLFUEem/49erT3sxIVjpzU5f6zmVcnijtDSrhoK2zVobOIikZJdH63jdAXOrvjf6eOUNQ==", + "dependencies": { + "tslib": "^2.6.3" + }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "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/@graphql-yoga/subscription/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==" + }, + "node_modules/@graphql-yoga/typed-event-target": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-3.0.0.tgz", + "integrity": "sha512-w+liuBySifrstuHbFrHoHAEyVnDFVib+073q8AeAJ/qqJfvFvAwUPLLtNohR/WDVRgSasfXtl3dcNuVJWN+rjg==", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.5.2" }, "engines": { - "node": ">= 8" + "node": ">=18.0.0" } }, - "node_modules/@parcel/watcher": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", - "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", - "dev": true, - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "engines": { - "node": ">= 10.0.0" + "node": ">=12.22" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.4.1", - "@parcel/watcher-darwin-arm64": "2.4.1", - "@parcel/watcher-darwin-x64": "2.4.1", - "@parcel/watcher-freebsd-x64": "2.4.1", - "@parcel/watcher-linux-arm-glibc": "2.4.1", - "@parcel/watcher-linux-arm64-glibc": "2.4.1", - "@parcel/watcher-linux-arm64-musl": "2.4.1", - "@parcel/watcher-linux-x64-glibc": "2.4.1", - "@parcel/watcher-linux-x64-musl": "2.4.1", - "@parcel/watcher-win32-arm64": "2.4.1", - "@parcel/watcher-win32-ia32": "2.4.1", - "@parcel/watcher-win32-x64": "2.4.1" + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz", - "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "license": "Apache-2.0", "engines": { - "node": ">= 10.0.0" + "node": ">=18.18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz", - "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==", - "cpu": [ - "arm64" - ], + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, + "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, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=12" } }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz", - "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==", - "cpu": [ - "x64" - ], + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">= 10.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz", - "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==", - "cpu": [ - "x64" - ], + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">= 10.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz", - "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==", - "cpu": [ - "arm" - ], + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz", - "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==", - "cpu": [ - "arm64" - ], + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz", - "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==", - "cpu": [ - "arm64" - ], + "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, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz", - "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==", - "cpu": [ - "x64" - ], + "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, - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10.0.0" + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz", - "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=6.0.0" } }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", - "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=6.0.0" } }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz", - "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", - "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@peculiar/asn1-schema": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz", - "integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==", - "dev": true, + "node_modules/@kamilkisiela/fast-url-parser": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", + "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==" + }, + "node_modules/@messageformat/core": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.3.0.tgz", + "integrity": "sha512-YcXd3remTDdeMxAlbvW6oV9d/01/DZ8DHUFwSttO3LMzIZj3iO0NRw+u1xlsNNORFI+u0EQzD52ZX3+Udi0T3g==", "dependencies": { - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2" + "@messageformat/date-skeleton": "^1.0.0", + "@messageformat/number-skeleton": "^1.0.0", + "@messageformat/parser": "^5.1.0", + "@messageformat/runtime": "^3.0.1", + "make-plural": "^7.0.0", + "safe-identifier": "^0.4.1" } }, - "node_modules/@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "dev": true, + "node_modules/@messageformat/date-skeleton": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.0.1.tgz", + "integrity": "sha512-jPXy8fg+WMPIgmGjxSlnGJn68h/2InfT0TNSkVx0IGXgp4ynnvYkbZ51dGWmGySEK+pBiYUttbQdu5XEqX5CRg==" + }, + "node_modules/@messageformat/number-skeleton": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz", + "integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==" + }, + "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": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=8.0.0" + "moo": "^0.5.1" } }, - "node_modules/@peculiar/webcrypto": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.5.tgz", - "integrity": "sha512-oDk93QCDGdxFRM8382Zdminzs44dg3M2+E5Np+JWkpqLDyJC9DviMh8F8mEJkYuUcUOGA5jHO5AJJ10MFWdbZw==", - "dev": true, + "node_modules/@messageformat/runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz", + "integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==", "dependencies": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.7.8" - }, - "engines": { - "node": ">=10.12.0" + "make-plural": "^7.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } + "node_modules/@microsoft/tsdoc": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", + "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", + "dev": true }, - "node_modules/@pm2/agent": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.3.tgz", - "integrity": "sha512-xkqqCoTf5VsciMqN0vb9jthW7olVAi4KRFNddCc7ZkeJZ3i8QwZANr4NSH2H5DvseRFHq7MiPspRY/EWAFWWTg==", + "node_modules/@microsoft/tsdoc-config": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz", + "integrity": "sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==", + "dev": true, "dependencies": { - "async": "~3.2.0", - "chalk": "~3.0.0", - "dayjs": "~1.8.24", - "debug": "~4.3.1", - "eventemitter2": "~5.0.1", - "fast-json-patch": "^3.0.0-1", - "fclone": "~1.0.11", - "nssocket": "0.6.0", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", - "proxy-agent": "~6.3.0", - "semver": "~7.5.0", - "ws": "~7.4.0" + "@microsoft/tsdoc": "0.15.0", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" } }, - "node_modules/@pm2/agent/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/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@pm2/agent/node_modules/dayjs": { - "version": "1.8.36", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", - "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==" + "node_modules/@microsoft/tsdoc-config/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/@pm2/agent/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/@mongodb-js/saslprep": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", + "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "sparse-bitfield": "^3.0.3" } }, - "node_modules/@pm2/agent/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==", + "node_modules/@mui/base": { + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@pm2/agent/node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "node_modules/@mui/core-downloads-tracker": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", + "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", + "integrity": "sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, "engines": { - "node": ">=8.3.0" + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { + "@types/react": { "optional": true } } }, - "node_modules/@pm2/io": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.0.0.tgz", - "integrity": "sha512-sKUEgZoQ5/jRwTyMB1I7u2wXL6dG0j/F/M4ANJ7dJCApfW8nWC0RElMW2siEKvZ79iplIPAaWV27oyBoerEflw==", + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.169", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.169.tgz", + "integrity": "sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA==", "dependencies": { - "async": "~2.6.1", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "require-in-the-middle": "^5.0.0", - "semver": "~7.5.4", - "shimmer": "^1.2.0", - "signal-exit": "^3.0.3", - "tslib": "1.9.3" + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" }, "engines": { - "node": ">=6.0" - } - }, - "node_modules/@pm2/io/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dependencies": { - "lodash": "^4.17.14" + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": ">=5.15.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } } }, - "node_modules/@pm2/io/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" - }, - "node_modules/@pm2/io/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/@mui/material": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", + "integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==", "dependencies": { - "yallist": "^4.0.0" + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.16.7", + "@mui/system": "^5.16.7", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } } }, - "node_modules/@pm2/io/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==", + "node_modules/@mui/private-theming": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.16.6", + "prop-types": "^15.8.1" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@pm2/io/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/@pm2/io/node_modules/tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" - }, - "node_modules/@pm2/js-api": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", - "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", + "node_modules/@mui/styled-engine": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", "dependencies": { - "async": "^2.6.3", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "extrareqp2": "^1.0.0", - "ws": "^7.0.0" + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" }, "engines": { - "node": ">=4.0" + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } } }, - "node_modules/@pm2/js-api/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/@mui/system": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/@pm2/js-api/node_modules/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" - }, - "node_modules/@pm2/js-api/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, "engines": { - "node": ">=8.3.0" + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { - "bufferutil": { + "@emotion/react": { "optional": true }, - "utf-8-validate": { + "@emotion/styled": { + "optional": true + }, + "@types/react": { "optional": true } } }, - "node_modules/@pm2/pm2-version-check": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", - "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", - "dependencies": { - "debug": "^4.3.1" + "node_modules/@mui/types": { + "version": "7.2.18", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz", + "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": 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==", + "node_modules/@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=12.0.0" + }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": 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/@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==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } }, - "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/@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==", + "engines": { + "node": ">= 8" + } }, - "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/@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==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", + "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.4.1", + "@parcel/watcher-darwin-arm64": "2.4.1", + "@parcel/watcher-darwin-x64": "2.4.1", + "@parcel/watcher-freebsd-x64": "2.4.1", + "@parcel/watcher-linux-arm-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-musl": "2.4.1", + "@parcel/watcher-linux-x64-glibc": "2.4.1", + "@parcel/watcher-linux-x64-musl": "2.4.1", + "@parcel/watcher-win32-arm64": "2.4.1", + "@parcel/watcher-win32-ia32": "2.4.1", + "@parcel/watcher-win32-x64": "2.4.1" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", + "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz", + "integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==", + "dev": true, + "dependencies": { + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.5.tgz", + "integrity": "sha512-oDk93QCDGdxFRM8382Zdminzs44dg3M2+E5Np+JWkpqLDyJC9DviMh8F8mEJkYuUcUOGA5jHO5AJJ10MFWdbZw==", + "dev": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.7.8" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pm2/agent": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.3.tgz", + "integrity": "sha512-xkqqCoTf5VsciMqN0vb9jthW7olVAi4KRFNddCc7ZkeJZ3i8QwZANr4NSH2H5DvseRFHq7MiPspRY/EWAFWWTg==", + "dependencies": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.3.1", + "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.0.0-1", + "fclone": "~1.0.11", + "nssocket": "0.6.0", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.0", + "proxy-agent": "~6.3.0", + "semver": "~7.5.0", + "ws": "~7.4.0" + } + }, + "node_modules/@pm2/agent/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@pm2/agent/node_modules/dayjs": { + "version": "1.8.36", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", + "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==" + }, + "node_modules/@pm2/agent/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@pm2/io": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.0.0.tgz", + "integrity": "sha512-sKUEgZoQ5/jRwTyMB1I7u2wXL6dG0j/F/M4ANJ7dJCApfW8nWC0RElMW2siEKvZ79iplIPAaWV27oyBoerEflw==", + "dependencies": { + "async": "~2.6.1", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "~7.5.4", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@pm2/io/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/io/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + }, + "node_modules/@pm2/io/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "node_modules/@pm2/js-api": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", + "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", + "dependencies": { + "async": "^2.6.3", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "extrareqp2": "^1.0.0", + "ws": "^7.0.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + }, + "node_modules/@pm2/js-api/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "dependencies": { + "debug": "^4.3.1" + } + }, + "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/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@repeaterjs/repeater": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.5.tgz", + "integrity": "sha512-l3YHBLAol6d/IKnB9LhpD0cEZWAoe3eFKUyTYWmFmCO2Q/WOckxLQAUyMZWwZV2M/m3+4vgRoaolFqaII82/TA==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.26.0.tgz", + "integrity": "sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.26.0.tgz", + "integrity": "sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.26.0.tgz", + "integrity": "sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.26.0.tgz", + "integrity": "sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.26.0.tgz", + "integrity": "sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.26.0.tgz", + "integrity": "sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.26.0.tgz", + "integrity": "sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.26.0.tgz", + "integrity": "sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.26.0.tgz", + "integrity": "sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.26.0.tgz", + "integrity": "sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.26.0.tgz", + "integrity": "sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.26.0.tgz", + "integrity": "sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.26.0.tgz", + "integrity": "sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.26.0.tgz", + "integrity": "sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.26.0.tgz", + "integrity": "sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.26.0.tgz", + "integrity": "sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.26.0.tgz", + "integrity": "sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.26.0.tgz", + "integrity": "sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "node_modules/@shikijs/core": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.23.1.tgz", + "integrity": "sha512-NuOVgwcHgVC6jBVH5V7iblziw6iQbWWHrj5IlZI3Fqu2yx9awH7OIQkXIcsHsUmY19ckwSgUMgrqExEyP5A0TA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/engine-javascript": "1.23.1", + "@shikijs/engine-oniguruma": "1.23.1", + "@shikijs/types": "1.23.1", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.23.1.tgz", + "integrity": "sha512-i/LdEwT5k3FVu07SiApRFwRcSJs5QM9+tod5vYCPig1Ywi8GR30zcujbxGQFJHwYD7A5BUqagi8o5KS+LEVgBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/types": "1.23.1", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-es": "0.4.1" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.23.1.tgz", + "integrity": "sha512-KQ+lgeJJ5m2ISbUZudLR1qHeH3MnSs2mjFg7bnencgs5jDVPeJ2NVDJ3N5ZHbcTsOIh0qIueyAJnwg7lg7kwXQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/types": "1.23.1", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.23.1.tgz", + "integrity": "sha512-98A5hGyEhzzAgQh2dAeHKrWW4HfCMeoFER2z16p5eJ+vmPeF6lZ/elEne6/UCU551F/WqkopqRsr1l2Yu6+A0g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", + "license": "MIT", + "peer": true + }, + "node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz", + "integrity": "sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz", + "integrity": "sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==", + "dependencies": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.10.tgz", + "integrity": "sha512-Uh0Sz9gdUuz538nvkPiyv1DZRX9+D15EKDtnQP5rYVAzM/dnYk3P8cg73jcxyOitPgT3mE3OVj7ky7sibzHWkw==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.1.tgz", + "integrity": "sha512-DujtuDA7BGEKExJ05W5OdxCoyekcKT3Rhg1ZGeiUWaz2BJIWXjZmsG/DIP4W48GHno7AQwRsaCb8NcBgH3QZpg==", + "dependencies": { + "@smithy/middleware-serde": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-stream": "^3.2.1", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.5.tgz", + "integrity": "sha512-4FTQGAsuwqTzVMmiRVTn0RR9GrbRfkP0wfu/tXWVHd2LgNpTY0uglQpIScXK4NaEyXbB3JmZt8gfVqO50lP8wg==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/property-provider": "^3.1.8", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.7.tgz", + "integrity": "sha512-kVSXScIiRN7q+s1x7BrQtZ1Aa9hvvP9FeCqCdBxv37GimIHgBCOnZ5Ip80HLt0DhnAKpiobFdGqTFgbaJNrazA==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.6.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.11.tgz", + "integrity": "sha512-Pd1Wnq3CQ/v2SxRifDUihvpXzirJYbbtXfEnnLV/z0OGCTx/btVX74P86IgrZkjOydOASBGXdPpupYQI+iO/6A==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.10", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.8.tgz", + "integrity": "sha512-zkFIG2i1BLbfoGQnf1qEeMqX0h5qAznzaZmMVNnvPZz9J5AWBPkOMckZWPedGUPcVITacwIdQXoPcdIQq5FRcg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.10.tgz", + "integrity": "sha512-hjpU1tIsJ9qpcoZq9zGHBJPBOeBGYt+n8vfhDwnITPhEre6APrvqq/y3XMDEGUT2cWQ4ramNqBPRbx3qn55rhw==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.10", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.10.tgz", + "integrity": "sha512-ewG1GHbbqsFZ4asaq40KmxCmXO+AFSM1b+DcO2C03dyJj/ZH71CiTg853FSE/3SHK9q3jiYQIFjlGSwfxQ9kww==", + "dependencies": { + "@smithy/eventstream-codec": "^3.1.7", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", + "dependencies": { + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.7.tgz", + "integrity": "sha512-4yNlxVNJifPM5ThaA5HKnHkn7JhctFUHvcaz6YXxHlYOSIrzI6VKQPTN8Gs1iN5nqq9iFcwIR9THqchUCouIfg==", + "dependencies": { + "@smithy/chunked-blob-reader": "^4.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.1", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-node": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.8.tgz", + "integrity": "sha512-tlNQYbfpWXHimHqrvgo14DrMAgUBua/cNoz9fMYcDmYej7MAmUcjav/QKQbFc3NrcPxeJ7QClER4tWZmfwoPng==", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.7.tgz", + "integrity": "sha512-xMAsvJ3hLG63lsBVi1Hl6BBSfhd8/Qnp8fC06kjOpJvyyCEXdwHITa5Kvdsk6gaAXLhbZMhQMIGvgUbfnJDP6Q==", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.8.tgz", + "integrity": "sha512-7Qynk6NWtTQhnGTTZwks++nJhQ1O54Mzi7fz4PqZOiYXb4Z1Flpb2yRvdALoggTS8xjtohWUM+RygOtB30YL3Q==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "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/@smithy/md5-js": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.8.tgz", + "integrity": "sha512-LwApfTK0OJ/tCyNUXqnWCKoE2b4rDSr4BJlDAVCkiWYeHESr+y+d5zlAanuLW6fnitVJRD/7d9/kN/ZM9Su4mA==", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } }, - "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==", + "node_modules/@smithy/middleware-content-length": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.10.tgz", + "integrity": "sha512-T4dIdCs1d/+/qMpwhJ1DzOhxCZjZHbHazEPJWdB4GDi2HjIZllVzeBEcdJUN0fomV8DURsgOyrbEUzg3vzTaOg==", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "node_modules/@smithy/middleware-endpoint": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", + "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-middleware": "^3.0.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "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/@smithy/middleware-retry": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", + "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/service-error-classification": "^3.0.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } }, - "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/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } }, - "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/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "node_modules/@smithy/middleware-stack": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", + "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "peerDependencies": { - "@redis/client": "^1.0.0" + "node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@redis/client": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", - "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14" + "node": ">=16.0.0" } }, - "node_modules/@redis/graph": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", - "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", - "peerDependencies": { - "@redis/client": "^1.0.0" + "node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@redis/json": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", - "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", - "peerDependencies": { - "@redis/client": "^1.0.0" + "node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@redis/search": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", - "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", - "peerDependencies": { - "@redis/client": "^1.0.0" + "node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@redis/time-series": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", - "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", - "peerDependencies": { - "@redis/client": "^1.0.0" + "node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@repeaterjs/repeater": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.5.tgz", - "integrity": "sha512-l3YHBLAol6d/IKnB9LhpD0cEZWAoe3eFKUyTYWmFmCO2Q/WOckxLQAUyMZWwZV2M/m3+4vgRoaolFqaII82/TA==" + "node_modules/@smithy/service-error-classification": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", + "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", + "dependencies": { + "@smithy/types": "^3.6.0" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", - "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] + "node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", - "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] + "node_modules/@smithy/signature-v4": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", + "integrity": "sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", - "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@smithy/smithy-client": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", + "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", - "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", - "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", + "dependencies": { + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", - "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", - "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", - "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.25.tgz", + "integrity": "sha512-fRw7zymjIDt6XxIsLwfJfYUfbGoO9CmCJk6rjJ/X5cd20+d2Is7xjU5Kt/AiDt6hX8DAf5dztmfP5O82gR9emA==", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", - "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-defaults-mode-node": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.25.tgz", + "integrity": "sha512-H3BSZdBDiVZGzt8TG51Pd2FvFO0PAx/A0mJ0EH8a13KJ6iUCdYnw/Dk/MdC1kTd0eUuUGisDFaxXVXo4HHFL1g==", + "dependencies": { + "@smithy/config-resolver": "^3.0.10", + "@smithy/credential-provider-imds": "^3.2.5", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/property-provider": "^3.1.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", - "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-endpoints": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.4.tgz", + "integrity": "sha512-kPt8j4emm7rdMWQyL0F89o92q10gvCUa6sBkBtDJ7nV2+P7wpXczzOfoDJ49CKXe5CCqb8dc1W+ZdLlrKzSAnQ==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", - "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", - "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", - "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-retry": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", + "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", + "dependencies": { + "@smithy/service-error-classification": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", - "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", + "dependencies": { + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", - "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", - "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/@shikijs/core": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.3.tgz", - "integrity": "sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==", - "peer": true, + "node_modules/@smithy/util-waiter": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.7.tgz", + "integrity": "sha512-d5yGlQtmN/z5eoTtIYgkvOw27US2Ous4VycnXatyoImIF9tzlcpnKqQ/V7qhvJmb2p6xZne1NopCLakdTnkBBQ==", "dependencies": { - "@types/hast": "^3.0.4" + "@smithy/abort-controller": "^3.1.6", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/@tokenizer/token": { @@ -5567,9 +7077,9 @@ } }, "node_modules/@types/cls-hooked": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@types/cls-hooked/-/cls-hooked-4.3.8.tgz", - "integrity": "sha512-tf/7H883gFA6MPlWI15EQtfNZ+oPL0gLKkOlx9UHFrun1fC/FkuyNBpTKq1B5E3T4fbvjId6WifHUdSGsMMuPg==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@types/cls-hooked/-/cls-hooked-4.3.9.tgz", + "integrity": "sha512-CMtHMz6Q/dkfcHarq9nioXH8BDPP+v5xvd+N90lBQ2bdmu06UvnLDqxTKoOJzz4SzIwb/x9i4UXGAAcnUDuIvg==", "dev": true, "dependencies": { "@types/node": "*" @@ -5588,6 +7098,12 @@ "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==" }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true + }, "node_modules/@types/cookies": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", @@ -5615,9 +7131,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/express": { @@ -5632,9 +7148,9 @@ } }, "node_modules/@types/express-rate-limit": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-6.0.0.tgz", - "integrity": "sha512-nZxo3nwU20EkTl/f2eGdndQkDIJYwkXIX4S3Vrp2jMdSdFJ6AWtIda8gOz0wiMuOFoeH/UUlCAiacz3x3eWNFA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-6.0.2.tgz", + "integrity": "sha512-e1xZLOOlxCDvplAGq7rDcXtbdBu2CWRsMjaIu1LVqGxWtKvwr884YE5mPs3IvHeG/OMDhf24oTaqG5T1bV3rBQ==", "deprecated": "This is a stub types definition. express-rate-limit provides its own type definitions, so you do not need this installed.", "dev": true, "dependencies": { @@ -5689,6 +7205,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", "peer": true, "dependencies": { "@types/unist": "*" @@ -5739,9 +7256,9 @@ "dev": true }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", - "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", "dev": true, "dependencies": { "@types/node": "*" @@ -5776,17 +7293,32 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", - "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", - "dev": true, - "license": "MIT" + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "dev": true }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -5811,12 +7343,21 @@ "@types/node": "*" } }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { - "version": "22.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz", - "integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/node-fetch": { @@ -5829,9 +7370,9 @@ } }, "node_modules/@types/nodemailer": { - "version": "6.4.15", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", - "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "version": "6.4.16", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz", + "integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -5848,9 +7389,9 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "node_modules/@types/qs": { "version": "6.9.14", @@ -5863,28 +7404,22 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.2.69", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.69.tgz", - "integrity": "sha512-W1HOMUWY/1Yyw0ba5TkCV+oqynRjG7BnteBB+B7JmAK7iw3l2SW+VGOxL+akPweix6jk2NNJtyJKpn4TkpfK3Q==", + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", "dependencies": { "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" - }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -5904,6 +7439,28 @@ "@types/node": "*" } }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/through": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", @@ -5919,9 +7476,10 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, "node_modules/@types/unist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", - "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT", "peer": true }, "node_modules/@types/uuid": { @@ -5931,9 +7489,9 @@ "dev": true }, "node_modules/@types/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==", + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==", "dev": true }, "node_modules/@types/webidl-conversions": { @@ -5958,9 +7516,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dependencies": { "@types/yargs-parser": "*" } @@ -6047,14 +7605,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", - "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", - "dependencies": { - "@typescript-eslint/scope-manager": "8.0.1", - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/typescript-estree": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", + "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", + "dependencies": { + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/typescript-estree": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4" }, "engines": { @@ -6074,12 +7632,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", - "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", + "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1" + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6179,9 +7737,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", - "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", + "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -6191,14 +7749,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", - "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", + "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -6332,11 +7890,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", - "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", + "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", "dependencies": { - "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/types": "8.11.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -6350,24 +7908,26 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "license": "ISC", + "peer": true }, "node_modules/@vitest/coverage-v8": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz", - "integrity": "sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", + "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.5", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.10", - "magicast": "^0.3.4", - "std-env": "^3.7.0", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -6375,28 +7935,66 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "2.0.5" + "@vitest/browser": "2.1.5", + "vitest": "2.1.5" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, + "node_modules/@vitest/coverage-v8/node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true + }, "node_modules/@vitest/expect": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", - "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", + "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", "dev": true, "dependencies": { - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", + "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/@vitest/pretty-format": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", - "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", "dev": true, "dependencies": { "tinyrainbow": "^1.2.0" @@ -6406,12 +8004,12 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", - "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", + "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", "dev": true, "dependencies": { - "@vitest/utils": "2.0.5", + "@vitest/utils": "2.1.5", "pathe": "^1.1.2" }, "funding": { @@ -6419,13 +8017,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", - "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", + "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.5", - "magic-string": "^0.30.10", + "@vitest/pretty-format": "2.1.5", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -6433,26 +8031,25 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", - "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", + "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", "dev": true, "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", + "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.5", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -6492,52 +8089,48 @@ } }, "node_modules/@whatwg-node/server": { - "version": "0.9.34", - "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.34.tgz", - "integrity": "sha512-1sHRjqUtZIyTR2m2dS/dJpzS5OcNDpPuUSVDa2PoEgzYVKr4GsqJaYtRaEXXFohvvyh6PkouYCc1rE7jMDWVCA==", + "version": "0.9.55", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.55.tgz", + "integrity": "sha512-FW04dJZfgBGaGoHQosCTeSOXKksCVzMLMV5YZPMpUfEmkH8VeDjCIMguvw2cKgrjnLjwQ1J3irLg2eNQbLxLNg==", "dependencies": { - "@whatwg-node/fetch": "^0.9.17", - "tslib": "^2.3.1" + "@whatwg-node/fetch": "^0.10.0", + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@whatwg-node/server/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@whatwg-node/server/node_modules/@whatwg-node/fetch": { - "version": "0.9.18", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.18.tgz", - "integrity": "sha512-hqoz6StCW+AjV/3N+vg0s1ah82ptdVUb9nH2ttj3UbySOXUvytWw2yqy8c1cKzyRk6mDD00G47qS3fZI9/gMjg==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.10.1.tgz", + "integrity": "sha512-gmPOLrsjSZWEZlr9Oe5+wWFBq3CG6fN13rGlM91Jsj/vZ95G9CCvrORGBAxMXy0AJGiC83aYiHXn3JzTzXQmbA==", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", + "@whatwg-node/node-fetch": "^0.7.1", "urlpattern-polyfill": "^10.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@whatwg-node/server/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", - "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.7.2.tgz", + "integrity": "sha512-OAAEIbyspvQwkcRGutYN3D0a+hzQogvcZ7I3hf6vg742ZEq52yMJTGtkwjl3KZRmzzUltd/oEMxEGsXFLjnuLQ==", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, + "node_modules/@whatwg-node/server/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==" + }, "node_modules/@whatwg-node/server/node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", @@ -6556,9 +8149,10 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -6702,6 +8296,11 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -6985,9 +8584,9 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -7195,9 +8794,9 @@ "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -7207,7 +8806,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -7235,6 +8834,11 @@ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -7455,847 +9059,1247 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, - "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/change-case-all": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.15.tgz", + "integrity": "sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==", + "dev": true, + "dependencies": { + "change-case": "^4.1.2", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lower-case": "^2.0.2", + "lower-case-first": "^2.0.2", + "sponge-case": "^1.0.1", + "swap-case": "^2.0.2", + "title-case": "^3.0.3", + "upper-case": "^2.0.2", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-tableau": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", + "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", + "dependencies": { + "chalk": "3.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/cli-tableau/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { "node": ">=12" } }, - "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/cliui/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==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/change-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, - "dependencies": { - "camel-case": "^4.1.2", - "capital-case": "^1.0.4", - "constant-case": "^3.0.4", - "dot-case": "^3.0.4", - "header-case": "^2.0.4", - "no-case": "^3.0.4", - "param-case": "^3.0.4", - "pascal-case": "^3.1.2", - "path-case": "^3.0.4", - "sentence-case": "^3.0.4", - "snake-case": "^3.0.4", - "tslib": "^2.0.3" + "engines": { + "node": ">=0.8" } }, - "node_modules/change-case-all": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.15.tgz", - "integrity": "sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==", + "node_modules/cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha512-XVb0RPmHQyy35Tz9z34gvtUcBKUK8A/1xkGCyeFc9B0C7Zr5SysgFaswRVdwI5NEMcO+3JKlIDGIOgERSn9NdA==", "dev": true, "dependencies": { - "change-case": "^4.1.2", - "is-lower-case": "^2.0.2", - "is-upper-case": "^2.0.2", - "lower-case": "^2.0.2", - "lower-case-first": "^2.0.2", - "sponge-case": "^1.0.1", - "swap-case": "^2.0.2", - "title-case": "^3.0.3", - "upper-case": "^2.0.2", - "upper-case-first": "^2.0.2" + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" } }, - "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/cls-hooked": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", + "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", + "dependencies": { + "async-hook-jl": "^1.7.6", + "emitter-listener": "^1.0.1", + "semver": "^5.4.1" + }, + "engines": { + "node": "^4.7 || >=6.9 || >=7.3 || >=8.2.1" + } }, - "node_modules/charm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" + "node_modules/cls-hooked/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } }, - "node_modules/check-error": { + "node_modules/clsx": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { - "node": ">= 16" + "node": ">=6" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "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" - }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", "engines": { - "node": ">= 8.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", + "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "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==", + "dependencies": { + "color-name": "~1.1.4" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/chokidar/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/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/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dependencies": { - "is-glob": "^4.0.1" + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/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==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 0.8" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=18" } }, - "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/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commonmark": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.30.0.tgz", + "integrity": "sha512-j1yoUo4gxPND1JWV9xj5ELih0yMv1iCWDG6eEQIPLSWLxzCXiFoyS7kvB+WwU+tZMf4snwJMMtaubV0laFpiBA==", "dependencies": { - "restore-cursor": "^3.1.0" + "entities": "~2.0", + "mdurl": "~1.0.1", + "minimist": ">=1.2.2", + "string.prototype.repeat": "^0.2.0" + }, + "bin": { + "commonmark": "bin/commonmark" }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "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, - "engines": { - "node": ">=6" - }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "node_modules/cli-tableau": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", - "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", "dependencies": { - "chalk": "3.0.0" - }, + "source-map": "^0.6.1" + } + }, + "node_modules/concat-with-sourcemaps/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==", "engines": { - "node": ">=8.10.0" + "node": ">=0.10.0" } }, - "node_modules/cli-tableau/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/concurrently": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", + "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "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==", + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "dev": true, - "engines": { - "node": ">= 10" + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "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": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "safe-buffer": "5.2.1" }, "engines": { - "node": ">=12" + "node": ">= 0.6" } }, - "node_modules/cliui/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==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.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" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "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==", "engines": { - "node": ">=0.8" + "node": ">= 0.6" } }, - "node_modules/cls-bluebird": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", - "integrity": "sha512-XVb0RPmHQyy35Tz9z34gvtUcBKUK8A/1xkGCyeFc9B0C7Zr5SysgFaswRVdwI5NEMcO+3JKlIDGIOgERSn9NdA==", - "dev": true, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "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-paste": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/copy-paste/-/copy-paste-1.5.3.tgz", + "integrity": "sha512-qOnFo+8l8vemGmdcoCiD7gPTefkXEg2rivYE+EBtuKOj754eFivkGhGAM9e/xqShrpuVE11evSxGnHwVAUK1Iw==", "dependencies": { - "is-bluebird": "^1.0.2", - "shimmer": "^1.1.0" + "iconv-lite": "^0.4.8" } }, - "node_modules/cls-hooked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", - "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dependencies": { - "async-hook-jl": "^1.7.6", - "emitter-listener": "^1.0.1", - "semver": "^5.4.1" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": "^4.7 || >=6.9 || >=7.3 || >=8.2.1" - } - }, - "node_modules/cls-hooked/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" + "node": ">= 0.10" } }, - "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "engines": { - "node": ">=0.10.0" - } + "node_modules/croner": { + "version": "4.1.97", + "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" }, - "node_modules/coffee-script": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", - "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", - "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dependencies": { + "cross-spawn": "^7.0.1" + }, "bin": { - "cake": "bin/cake", - "coffee": "bin/coffee" + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" }, "engines": { - "node": ">=0.8.0" + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dev": true, "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" + "node-fetch": "^2.6.12" } }, - "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/cross-inspect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.0.tgz", + "integrity": "sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==", "dependencies": { - "color-name": "~1.1.4" + "tslib": "^2.4.0" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "node": ">=16.0.0" } }, - "node_modules/color/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/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dependencies": { - "color-name": "1.1.3" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "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/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "node_modules/culvert": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==" }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "engines": { - "node": ">= 0.8" + "node": ">= 14" } }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, "engines": { - "node": ">=4.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/commonmark": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.30.0.tgz", - "integrity": "sha512-j1yoUo4gxPND1JWV9xj5ELih0yMv1iCWDG6eEQIPLSWLxzCXiFoyS7kvB+WwU+tZMf4snwJMMtaubV0laFpiBA==", + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, "dependencies": { - "entities": "~2.0", - "mdurl": "~1.0.1", - "minimist": ">=1.2.2", - "string.prototype.repeat": "^0.2.0" - }, - "bin": { - "commonmark": "bin/commonmark" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "node_modules/dataloader": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", + "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==" }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, - "node_modules/concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "source-map": "^0.6.1" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/concat-with-sourcemaps/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/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "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==", + "dev": true + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "date-fns": "^2.30.0", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, "engines": { - "node": "^14.13.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + "node": ">=6" } }, - "node_modules/concurrently/node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" + "clone": "^1.0.2" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/concurrently/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, + "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": { - "has-flag": "^4.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/constant-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case": "^2.0.2" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "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/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dependencies": { - "safe-buffer": "5.2.1" + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 14" } }, - "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==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { - "node": ">= 0.6" + "node": ">=0.4.0" } }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", "engines": { - "node": ">= 0.6" + "node": ">=0.10" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "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/copy-paste": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/copy-paste/-/copy-paste-1.5.3.tgz", - "integrity": "sha512-qOnFo+8l8vemGmdcoCiD7gPTefkXEg2rivYE+EBtuKOj754eFivkGhGAM9e/xqShrpuVE11evSxGnHwVAUK1Iw==", - "dependencies": { - "iconv-lite": "^0.4.8" + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, + "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" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "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==", + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", "dev": true, - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/croner": { - "version": "4.1.97", - "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", - "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" - }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dependencies": { - "cross-spawn": "^7.0.1" - }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" + "detect-libc": "bin/detect-libc.js" }, "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" + "node": ">=0.10" } }, - "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dev": true, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "peer": true, "dependencies": { - "node-fetch": "^2.6.12" + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/cross-inspect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.0.tgz", - "integrity": "sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==", + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" + "asap": "^2.0.0", + "wrappy": "1" } }, - "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==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "node_modules/diacritics-map": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz", + "integrity": "sha512-3omnDTYrGigU0i4cJjvaKwD52B8aoqyX/NEIkukFFkogBemsIbhSa1O414fpTp5nuszJG6lvQ5vBvDVNCbSsaQ==", "engines": { - "node": ">= 8" + "node": ">=0.8.0" } }, - "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/culvert": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==" - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "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==", "dependencies": { - "assert-plus": "^1.0.0" + "path-type": "^4.0.0" }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "engines": { - "node": ">= 14" + "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/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "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": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://dotenvx.com" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, + "node_modules/dset": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz", + "integrity": "sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, + "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/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, - "node_modules/dataloader": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", - "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==" + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" } }, - "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "dev": true + "node_modules/electron-to-chromium": { + "version": "1.4.715", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", + "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==" }, - "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "node_modules/emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "shimmer": "^1.2.0" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, + "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/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT", + "peer": true + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "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==", - "dev": true - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "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==", + "dependencies": { + "ansi-colors": "^4.1.1" + }, "engines": { - "node": ">=6" + "node": ">=8.6" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "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/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, - "dependencies": { - "clone": "^1.0.2" + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", + "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", + "dev": true, "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -8304,444 +10308,495 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "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.0" + "node": ">= 0.4" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "engines": { - "node": ">=0.10" - } + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true }, - "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==", + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "node_modules/dependency-graph": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, "engines": { - "node": ">= 0.6.0" + "node": ">= 0.4" } }, - "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.8", - "npm": "1.2.8000 || >= 1.4.16" + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" } }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", "dev": true, + "hasInstallScript": true, + "license": "MIT", "bin": { - "detect-libc": "bin/detect-libc.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=0.10" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" } }, - "node_modules/diacritics-map": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz", - "integrity": "sha512-3omnDTYrGigU0i4cJjvaKwD52B8aoqyX/NEIkukFFkogBemsIbhSa1O414fpTp5nuszJG6lvQ5vBvDVNCbSsaQ==", + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=0.8.0" + "node": ">=18" } }, - "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==", - "dependencies": { - "path-type": "^4.0.0" - }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "^2.0.2" - }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.0.0" - } - }, - "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": ">=18" } }, - "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/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">=18" } }, - "node_modules/dset": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz", - "integrity": "sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==", + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">=18" } }, - "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/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ecc-jsbn/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.715", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", - "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==" - }, - "node_modules/emitter-listener": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", - "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", - "dependencies": { - "shimmer": "^1.2.0" + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "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/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "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==", - "dependencies": { - "ansi-colors": "^4.1.1" - }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.6" + "node": ">=18" } }, - "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/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "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": { - "is-arrayish": "^0.2.1" + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/es-abstract": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", - "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "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==", + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "es-errors": "^1.3.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "hasown": "^2.0.0" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "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/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/esbuild": { + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", - "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.1", - "@esbuild/android-arm": "0.23.1", - "@esbuild/android-arm64": "0.23.1", - "@esbuild/android-x64": "0.23.1", - "@esbuild/darwin-arm64": "0.23.1", - "@esbuild/darwin-x64": "0.23.1", - "@esbuild/freebsd-arm64": "0.23.1", - "@esbuild/freebsd-x64": "0.23.1", - "@esbuild/linux-arm": "0.23.1", - "@esbuild/linux-arm64": "0.23.1", - "@esbuild/linux-ia32": "0.23.1", - "@esbuild/linux-loong64": "0.23.1", - "@esbuild/linux-mips64el": "0.23.1", - "@esbuild/linux-ppc64": "0.23.1", - "@esbuild/linux-riscv64": "0.23.1", - "@esbuild/linux-s390x": "0.23.1", - "@esbuild/linux-x64": "0.23.1", - "@esbuild/netbsd-x64": "0.23.1", - "@esbuild/openbsd-arm64": "0.23.1", - "@esbuild/openbsd-x64": "0.23.1", - "@esbuild/sunos-x64": "0.23.1", - "@esbuild/win32-arm64": "0.23.1", - "@esbuild/win32-ia32": "0.23.1", - "@esbuild/win32-x64": "0.23.1" } }, "node_modules/escalade": { @@ -8798,42 +10853,39 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.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", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", @@ -8845,10 +10897,18 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -8884,9 +10944,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -8910,34 +10970,37 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { @@ -8994,15 +11057,16 @@ } }, "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==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -9019,24 +11083,67 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/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==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dependencies": { - "type-fest": "^0.20.2" + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9046,6 +11153,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9053,28 +11161,30 @@ "node": "*" } }, - "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/espree": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.1.0" + }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -9107,6 +11217,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -9192,37 +11303,46 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -9242,9 +11362,10 @@ } }, "node_modules/express-rate-limit": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.0.tgz", - "integrity": "sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz", + "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==", + "license": "MIT", "engines": { "node": ">= 16" }, @@ -9395,6 +11516,12 @@ "fast-decode-uri-component": "^1.0.1" } }, + "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/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -9404,6 +11531,27 @@ "punycode": "^1.3.2" } }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -9475,14 +11623,15 @@ } }, "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==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-type": { @@ -9517,12 +11666,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -9567,36 +11716,23 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "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==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", @@ -9676,6 +11812,20 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^2.0.0", + "once": "^1.4.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9716,13 +11866,15 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -9802,15 +11954,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-graphql-schema": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/get-graphql-schema/-/get-graphql-schema-2.1.2.tgz", @@ -10004,6 +12147,7 @@ "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", @@ -10034,6 +12178,7 @@ "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" @@ -10043,6 +12188,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10051,11 +12197,15 @@ } }, "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==", + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -10123,9 +12273,9 @@ } }, "node_modules/graphql-config": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-5.0.3.tgz", - "integrity": "sha512-BNGZaoxIBkv9yy6Y7omvsaBUHOzfFcII3UN++tpH8MGOKFPFkCPZuwx09ggANMt8FgyWP1Od8SWPmrUEZca4NQ==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-5.1.3.tgz", + "integrity": "sha512-RBhejsPjrNSuwtckRlilWzLVt2j8itl74W9Gke1KejDTz7oaA5kVd6wRn9zK9TS5mcmIYGxf7zN7a1ORMdxp1Q==", "dev": true, "dependencies": { "@graphql-tools/graphql-file-loader": "^8.0.0", @@ -10135,8 +12285,8 @@ "@graphql-tools/url-loader": "^8.0.0", "@graphql-tools/utils": "^10.0.0", "cosmiconfig": "^8.1.0", - "jiti": "^1.18.2", - "minimatch": "^4.2.3", + "jiti": "^2.0.0", + "minimatch": "^9.0.5", "string-env-interpolation": "^1.0.1", "tslib": "^2.4.0" }, @@ -10153,26 +12303,13 @@ } } }, - "node_modules/graphql-config/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/graphql-config/node_modules/minimatch": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.3.tgz", - "integrity": "sha512-lIUdtK5hdofgCTu3aT0sOaHsYR37viUuIc0rwnnDXImbwFRcumyLMeZaM0t0I/fgxS6s6JMfu0rLD1Wz9pv1ng==", + "node_modules/graphql-config/node_modules/jiti": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.0.tgz", + "integrity": "sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/graphql-depth-limit": { @@ -10190,9 +12327,9 @@ } }, "node_modules/graphql-markdown": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/graphql-markdown/-/graphql-markdown-7.0.0.tgz", - "integrity": "sha512-gJoc1gKxmZNa8gtUnR6a694Unm3QYGTX8we3DH/xvj0BavJWcGB+MNlg7A6PeP/BwcO9DpMIO+ElcrOOS+8R0g==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/graphql-markdown/-/graphql-markdown-7.3.0.tgz", + "integrity": "sha512-xTgP+W156DD3k55KpwvNUtV7RTJmGKQhvCzseUXSnJf7KrZozhyyxhq4bv1IiOJgGeRjt5zwHBbEcAEEWCacrg==", "dev": true, "dependencies": { "deep-diff": "^1.0.2", @@ -10264,9 +12401,9 @@ } }, "node_modules/graphql-upload": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-16.0.2.tgz", - "integrity": "sha512-enwIkZqUELdNH9lrjHlTNfj7gLitSa0EAX4TNXZtg2frnmQzPhpjH0l+6K7ft274fhoRCIcz8SKiNRJDf/cG4Q==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-17.0.0.tgz", + "integrity": "sha512-AI42S1UR1mdqg+LQ7KqGbrgcf4l9gpPu/R0drM4vSA5C94NfIjYyCeCdpktEledvZoAL8JURLLeB53++WACo1w==", "dependencies": { "@types/busboy": "^1.5.0", "@types/node": "*", @@ -10277,13 +12414,13 @@ "object-path": "^0.11.8" }, "engines": { - "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=22.0.0" }, "funding": { "url": "https://github.com/sponsors/jaydenseric" }, "peerDependencies": { - "@types/express": "^4.0.29", + "@types/express": "4.0.29 - 5", "@types/koa": "^2.11.4", "graphql": "^16.3.0" }, @@ -10297,18 +12434,21 @@ } }, "node_modules/graphql-voyager": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/graphql-voyager/-/graphql-voyager-2.0.0.tgz", - "integrity": "sha512-mlLUChRP1k6chHCfOMBBfnvuDnMrEQho0EYzuBhBV7oIZJPh97FqTAaly4jpUAMOFxSR5SBDpNLy0cLesHG9sg==", - "dependencies": { - "@emotion/react": "11.10.5", - "@emotion/styled": "11.10.5", - "@mui/icons-material": "5.11.0", - "@mui/lab": "5.0.0-alpha.114", - "@mui/material": "5.11.2", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/graphql-voyager/-/graphql-voyager-2.1.0.tgz", + "integrity": "sha512-CmYqMMK9aNpi7lMf1/9HvIj0Vyc01EokngZPPZgazH8DWWpVOWYt2OQfkzZVWm7ZgyMqMeKOyhNtjVZbOUNB8A==", + "dependencies": { + "@emotion/react": "11.13.3", + "@emotion/styled": "11.13.0", + "@mui/icons-material": "5.16.7", + "@mui/lab": "5.0.0-alpha.169", + "@mui/material": "5.16.7", "commonmark": "0.30.0", "svg-pan-zoom": "3.6.1" }, + "engines": { + "node": ">=20.0.0" + }, "funding": { "url": "https://github.com/graphql-kit/graphql-voyager?sponsor=1" }, @@ -10329,18 +12469,18 @@ } }, "node_modules/graphql-yoga": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.3.1.tgz", - "integrity": "sha512-n918QV6TF7xTjb9ASnozgsr4ydMc08c+x4eRAWKxxWVwSnzdP2xeN2zw1ljIzRD0ccSCNoBajGDKwcZkJDitPA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.7.0.tgz", + "integrity": "sha512-QyGVvFAvGhMrzjJvhjsxsyoE+e4lNrj5f5qOsRYJuWIjyw7tHfbBvybZIwzNOGY0aB5sgA8BlVvu5hxjdKJ5tQ==", "dependencies": { - "@envelop/core": "^5.0.0", - "@graphql-tools/executor": "^1.2.5", - "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.1.0", + "@envelop/core": "^5.0.1", + "@graphql-tools/executor": "^1.3.0", + "@graphql-tools/schema": "^10.0.4", + "@graphql-tools/utils": "^10.3.2", "@graphql-yoga/logger": "^2.0.0", - "@graphql-yoga/subscription": "^5.0.0", - "@whatwg-node/fetch": "^0.9.17", - "@whatwg-node/server": "^0.9.33", + "@graphql-yoga/subscription": "^5.0.1", + "@whatwg-node/fetch": "^0.9.18", + "@whatwg-node/server": "^0.9.44", "dset": "^3.1.1", "lru-cache": "^10.0.0", "tslib": "^2.5.2" @@ -10352,48 +12492,41 @@ "graphql": "^15.2.0 || ^16.0.0" } }, - "node_modules/graphql-yoga/node_modules/@whatwg-node/events": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.1.1.tgz", - "integrity": "sha512-AyQEn5hIPV7Ze+xFoXVU3QTHXVbWPrzaOkxtENMPMuNL6VVHrp4hHfDt9nrQpjO7BgvuM95dMtkycX5M/DZR3w==", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/graphql-yoga/node_modules/@whatwg-node/fetch": { - "version": "0.9.18", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.18.tgz", - "integrity": "sha512-hqoz6StCW+AjV/3N+vg0s1ah82ptdVUb9nH2ttj3UbySOXUvytWw2yqy8c1cKzyRk6mDD00G47qS3fZI9/gMjg==", + "version": "0.9.23", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.23.tgz", + "integrity": "sha512-7xlqWel9JsmxahJnYVUj/LLxWcnA93DR4c9xlw3U814jWTiYalryiH1qToik1hOxweKKRLi4haXHM5ycRksPBA==", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.7", + "@whatwg-node/node-fetch": "^0.6.0", "urlpattern-polyfill": "^10.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/graphql-yoga/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", - "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.6.0.tgz", + "integrity": "sha512-tcZAhrpx6oVlkEsRngeTEEE7I5/QdLjeEz4IlekabGaESP7+Dkm/6a9KcF1KdCBB7mO9PXtBkwCuTCt8+UPg8Q==", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", - "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/graphql-yoga/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/graphql-yoga/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==" }, "node_modules/graphql-yoga/node_modules/urlpattern-polyfill": { "version": "10.0.0", @@ -10548,6 +12681,44 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/header-case": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", @@ -10559,11 +12730,20 @@ } }, "node_modules/helmet": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", - "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.0.0.tgz", + "integrity": "sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==", "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/hexoid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/hoist-non-react-statics": { @@ -10585,6 +12765,17 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -10648,9 +12839,9 @@ } }, "node_modules/husky": { - "version": "9.1.5", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.5.tgz", - "integrity": "sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", "dev": true, "bin": { "husky": "bin.js" @@ -10719,6 +12910,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/image-hash": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/image-hash/-/image-hash-5.3.2.tgz", @@ -10796,6 +12993,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -11008,11 +13206,14 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11466,7 +13667,7 @@ "version": "1.21.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", - "dev": true, + "devOptional": true, "bin": { "jiti": "bin/jiti.js" } @@ -11537,7 +13738,8 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -11725,6 +13927,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -11797,6 +14000,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", "peer": true, "dependencies": { "uc.micro": "^2.0.0" @@ -11845,9 +14049,9 @@ } }, "node_modules/lint-staged/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "engines": { "node": ">=12" @@ -11912,9 +14116,9 @@ } }, "node_modules/lint-staged/node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true }, "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { @@ -12355,13 +14559,10 @@ } }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true }, "node_modules/lower-case": { "version": "2.0.2", @@ -12393,25 +14594,27 @@ "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "license": "MIT", "peer": true }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magicast": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", - "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.24.4", - "@babel/types": "^7.24.0", + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, @@ -12475,6 +14678,7 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", "peer": true, "dependencies": { "argparse": "^2.0.1", @@ -12492,6 +14696,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "peer": true, "engines": { "node": ">=0.12" @@ -12504,6 +14709,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT", "peer": true }, "node_modules/markdown-link": { @@ -12552,6 +14758,28 @@ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -12571,9 +14799,12 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -12613,6 +14844,100 @@ "node": ">= 0.6" } }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -12877,11 +15202,6 @@ } } }, - "node_modules/mongoose/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/moo": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", @@ -12946,9 +15266,37 @@ } }, "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==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } }, "node_modules/mustache": { "version": "4.2.0", @@ -12964,15 +15312,16 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/nanoid": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", - "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.js" }, @@ -13099,13 +15448,97 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nodemailer": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", - "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", "engines": { "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "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/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/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/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/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -13181,9 +15614,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13314,6 +15750,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -13341,6 +15778,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oniguruma-to-es": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.4.1.tgz", + "integrity": "sha512-rNcEohFz095QKGRovP/yqPIKc+nP+Sjs4YTHMv33nMePGKrq/r2eu9Yh4646M5XluGJsUnmwoXuiXE69KDs+fQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.0.0", + "regex-recursion": "^4.2.1" + } + }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -13590,6 +16039,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -13654,9 +16104,9 @@ } }, "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": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -13699,9 +16149,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "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.1", @@ -13915,9 +16365,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -13935,8 +16385,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -14020,6 +16470,17 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14060,6 +16521,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, + "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/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -14070,6 +16537,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -14094,11 +16562,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -14178,9 +16646,10 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "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==", + "license": "MIT", "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -14190,22 +16659,23 @@ } }, "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==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "peer": true, "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "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==" + "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==" }, "node_modules/react-transition-group": { "version": "4.4.5", @@ -14333,6 +16803,33 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/regex": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.0.2.tgz", + "integrity": "sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-4.2.1.tgz", + "integrity": "sha512-QHNZyZAeKdndD1G3bKAbBEKOSSK4KOHQrAJ01N1LJeb0SoH4DJIeFhp0uUpETgONifS4+P3sOgoA1dhzgrQvhA==", + "license": "MIT", + "peer": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT", + "peer": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -14680,12 +17177,12 @@ } }, "node_modules/rollup": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", - "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.26.0.tgz", + "integrity": "sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -14695,22 +17192,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.0", - "@rollup/rollup-android-arm64": "4.21.0", - "@rollup/rollup-darwin-arm64": "4.21.0", - "@rollup/rollup-darwin-x64": "4.21.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", - "@rollup/rollup-linux-arm-musleabihf": "4.21.0", - "@rollup/rollup-linux-arm64-gnu": "4.21.0", - "@rollup/rollup-linux-arm64-musl": "4.21.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", - "@rollup/rollup-linux-riscv64-gnu": "4.21.0", - "@rollup/rollup-linux-s390x-gnu": "4.21.0", - "@rollup/rollup-linux-x64-gnu": "4.21.0", - "@rollup/rollup-linux-x64-musl": "4.21.0", - "@rollup/rollup-win32-arm64-msvc": "4.21.0", - "@rollup/rollup-win32-ia32-msvc": "4.21.0", - "@rollup/rollup-win32-x64-msvc": "4.21.0", + "@rollup/rollup-android-arm-eabi": "4.26.0", + "@rollup/rollup-android-arm64": "4.26.0", + "@rollup/rollup-darwin-arm64": "4.26.0", + "@rollup/rollup-darwin-x64": "4.26.0", + "@rollup/rollup-freebsd-arm64": "4.26.0", + "@rollup/rollup-freebsd-x64": "4.26.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.26.0", + "@rollup/rollup-linux-arm-musleabihf": "4.26.0", + "@rollup/rollup-linux-arm64-gnu": "4.26.0", + "@rollup/rollup-linux-arm64-musl": "4.26.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.26.0", + "@rollup/rollup-linux-riscv64-gnu": "4.26.0", + "@rollup/rollup-linux-s390x-gnu": "4.26.0", + "@rollup/rollup-linux-x64-gnu": "4.26.0", + "@rollup/rollup-linux-x64-musl": "4.26.0", + "@rollup/rollup-win32-arm64-msvc": "4.26.0", + "@rollup/rollup-win32-ia32-msvc": "4.26.0", + "@rollup/rollup-win32-x64-msvc": "4.26.0", "fsevents": "~2.3.2" } }, @@ -14859,9 +17358,10 @@ "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -14882,9 +17382,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -14917,10 +17417,13 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "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/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/sentence-case": { "version": "3.0.4", @@ -14934,14 +17437,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -15047,12 +17550,17 @@ } }, "node_modules/shiki": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.10.3.tgz", - "integrity": "sha512-eneCLncGuvPdTutJuLyUGS8QNPAVFO5Trvld2wgEq1e002mwctAhJKeMGWtWVXOIEzmlcLRqcgPSorR6AVzOmQ==", + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.23.1.tgz", + "integrity": "sha512-8kxV9TH4pXgdKGxNOkrSMydn1Xf6It8lsle0fiqxf7a1149K1WGtdOu3Zb91T5r1JpvRPxqxU3C2XdZZXQnrig==", + "license": "MIT", "peer": true, "dependencies": { - "@shikijs/core": "1.10.3", + "@shikijs/core": "1.23.1", + "@shikijs/engine-javascript": "1.23.1", + "@shikijs/engine-oniguruma": "1.23.1", + "@shikijs/types": "1.23.1", + "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, @@ -15087,7 +17595,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", @@ -15120,6 +17629,30 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -15196,9 +17729,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -15221,6 +17754,17 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -15229,12 +17773,6 @@ "memory-pager": "^1.0.2" } }, - "node_modules/spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, "node_modules/sponge-case": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz", @@ -15295,7 +17833,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/standard-as-callback": { "version": "2.1.0", @@ -15433,6 +17972,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "peer": true, + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -15497,6 +18051,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/strtok3": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", @@ -15518,6 +18077,51 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, + "node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15680,6 +18284,13 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true }, "node_modules/tinypool": { @@ -15687,6 +18298,7 @@ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -15696,14 +18308,15 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", - "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "engines": { "node": ">=14.0.0" @@ -15730,14 +18343,6 @@ "node": ">=0.6.0" } }, - "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": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "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", @@ -15797,6 +18402,15 @@ "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==" }, + "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, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -15845,6 +18459,17 @@ "tree-kill": "cli.js" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -15900,9 +18525,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsx": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.0.tgz", - "integrity": "sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", + "integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", "dev": true, "dependencies": { "esbuild": "~0.23.0", @@ -16065,16 +18690,17 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typedoc": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.3.tgz", - "integrity": "sha512-6d2Sw9disvvpdk4K7VNjKr5/3hzijtfQVHRthhDqJgnhMHy1wQz4yPMJVKXElvnZhFr0nkzo+GzjXDTRV5yLpg==", + "version": "0.26.11", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.11.tgz", + "integrity": "sha512-sFEgRRtrcDl2FxVP58Ze++ZK2UQAEvtvvH8rRlig1Ja3o7dDaMHmaBfvJmdGnNEFaLTpQsN8dpvZaTqJSu/Ugw==", + "license": "Apache-2.0", "peer": true, "dependencies": { "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "shiki": "^1.9.1", - "yaml": "^2.4.5" + "shiki": "^1.16.2", + "yaml": "^2.5.1" }, "bin": { "typedoc": "bin/typedoc" @@ -16083,13 +18709,13 @@ "node": ">= 18" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.3.tgz", - "integrity": "sha512-esucQj79SFYOv0f5XVha7QWdLUH5C5HRlDf7Z8CXzHedmVPn7jox6Gt7FdoBXN8AFxyHpa3Lbuxu65Dobwt+4Q==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.7.tgz", + "integrity": "sha512-bLsQdweSm48P9j6kGqQ3/4GCH5zu2EnURSkkxqirNc+uVFE9YK825ogDw+WbNkRHIV6eZK/1U43gT7YfglyYOg==", "engines": { "node": ">= 18" }, @@ -16097,6 +18723,19 @@ "typedoc": "0.26.x" } }, + "node_modules/typedoc/node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "license": "ISC", + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -16136,6 +18775,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT", "peer": true }, "node_modules/unbox-primitive": { @@ -16162,616 +18802,373 @@ "node": ">=0.10.0" } }, + "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/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unixify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", - "integrity": "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==", - "dependencies": { - "normalize-path": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unixify/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "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.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/upper-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/urlpattern-polyfill": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", - "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", - "engines": { - "node": ">= 0.10" - } - }, - "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==", - "engines": { - "node": ">=12" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/verror/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": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "node_modules/vite": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", - "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", - "dev": true, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "peer": true, "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.41", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" + "@types/unist": "^3.0.0" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", - "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", - "dev": true, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "peer": true, "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.5", - "pathe": "^1.1.2", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" + "@types/unist": "^3.0.0" }, - "engines": { - "node": "^18.0.0 || >=20.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "engines": { - "node": ">=12" + "node": ">= 10.0.0" } }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/unixify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz", + "integrity": "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==", + "dependencies": { + "normalize-path": "^2.1.1" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/unixify/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "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" + } ], - "engines": { - "node": ">=12" + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.0.3" } }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.0.3" } }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" } }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "node_modules/urlpattern-polyfill": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "engines": { - "node": ">=12" + "node": ">= 0.4.0" } }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" + "node_modules/uuid": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.1.tgz", + "integrity": "sha512-wt9UB5EcLhnboy1UvA1mvGPXkIIrHSu+3FmUksARfdVw9tuPf3CH/CohxO0Su1ApoKAeT6BVzAJIvjTuQVSmuQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "engines": { - "node": ">=12" + "bin": { + "uuid": "dist/esm/bin/uuid" } }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", "engines": { - "node": ">=12" + "node": ">= 0.10" } }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "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==", "engines": { "node": ">=12" } }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" ], - "engines": { - "node": ">=12" + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" } }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" + "node_modules/verror/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": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=12" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } } }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], + "node_modules/vite-node": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", + "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, "engines": { - "node": ">=12" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { @@ -16829,29 +19226,30 @@ } }, "node_modules/vitest": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", - "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.5", - "@vitest/pretty-format": "^2.0.5", - "@vitest/runner": "2.0.5", - "@vitest/snapshot": "2.0.5", - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", - "debug": "^4.3.5", - "execa": "^8.0.1", - "magic-string": "^0.30.10", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", + "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.5", + "@vitest/mocker": "2.1.5", + "@vitest/pretty-format": "^2.1.5", + "@vitest/runner": "2.1.5", + "@vitest/snapshot": "2.1.5", + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", - "std-env": "^3.7.0", - "tinybench": "^2.8.0", - "tinypool": "^1.0.0", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.5", + "vite-node": "2.1.5", "why-is-node-running": "^2.3.0" }, "bin": { @@ -16866,8 +19264,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.5", - "@vitest/ui": "2.0.5", + "@vitest/browser": "2.1.5", + "@vitest/ui": "2.1.5", "happy-dom": "*", "jsdom": "*" }, @@ -16892,6 +19290,12 @@ } } }, + "node_modules/vitest/node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true + }, "node_modules/vizion": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", @@ -17033,6 +19437,7 @@ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -17045,9 +19450,10 @@ } }, "node_modules/winston": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.2.tgz", - "integrity": "sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", + "integrity": "sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==", + "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", @@ -17158,7 +19564,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/ws": { "version": "8.18.0", @@ -17205,6 +19612,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "dev": true, "bin": { "yaml": "bin.mjs" }, @@ -17269,6 +19677,17 @@ "dependencies": { "zod": "^3.20.2" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index fa4e9de7279..a2f250820ab 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,17 @@ "main": "./build/server.js", "scripts": { "build": "tsc --pretty --project tsconfig.build.json", - "dev": "concurrently \"tsx --watch ./src/index.ts\" \"graphql-codegen --watch\"", + "dev": "concurrently \"nodemon --exec tsx ./src/index.ts\" \"graphql-codegen --watch\"", + "minio:check": "tsc ./src/minioInstallationCheck.ts --outDir ./build && node ./build/minioInstallationCheck.js", + "dev:with-minio": "concurrently \"npm run minio:check\" \"nodemon --exec tsx ./src/index.ts\" \"graphql-codegen --watch\"", + "start:with-minio": "concurrently \"npm run minio:check\" \"cross-env pm2-runtime start ./build/server.js\"", "prebuild": "graphql-codegen && rimraf ./build", "prod": "cross-env NODE_ENV=production pm2-runtime start ./build/server.js", "start": "cross-env pm2-runtime start ./build/server.js --watch", "setup": "tsx setup.ts", "test": "vitest run --pool=threads --no-file-parallelism --coverage", "typecheck": "graphql-codegen && tsc --noEmit --pretty", - "lint:check": "eslint . --max-warnings=1500", + "lint:check": "eslint . --max-warnings=1500 && python .github/workflows/eslint_disable_check.py", "lint:fix": "eslint . --fix", "lint-staged": "lint-staged", "format:fix": "prettier --write \"**/*.{ts,tsx,json,scss,css}\"", @@ -44,38 +47,39 @@ }, "homepage": "https://github.com/PalisadoesFoundation/talawa-api#readme", "dependencies": { - "@apollo/server": "^4.11.0", - "@faker-js/faker": "^8.2.0", - "@graphql-inspector/cli": "^5.0.6", - "@graphql-tools/resolvers-composition": "^7.0.1", + "@apollo/server": "^4.11.2", + "@aws-sdk/client-s3": "^3.691.0", + "@faker-js/faker": "^9.0.1", + "@graphql-inspector/cli": "^5.0.7", + "@graphql-tools/resolvers-composition": "^7.0.2", "@graphql-tools/schema": "^10.0.6", "@graphql-tools/utils": "^10.3.2", "@parcel/watcher": "^2.4.1", "@types/graphql-upload": "^16.0.5", - "@types/yargs": "^17.0.32", + "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.2.0", - "@typescript-eslint/parser": "^8.0.1", - "axios": "^1.7.4", + "@typescript-eslint/parser": "^8.11.0", + "axios": "^1.7.7", "bcryptjs": "^2.4.3", "bluebird": "3.7.2", "cls-hooked": "^4.2.2", "copy-paste": "^1.5.3", "cors": "^2.8.5", "cross-env": "^7.0.3", - "date-fns": "^3.3.1", + "date-fns": "^4.1.0", "dotenv": "^16.4.1", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^7.4.0", + "express-rate-limit": "^7.4.1", "graphql": "^16.9.0", "graphql-depth-limit": "^1.1.0", "graphql-scalars": "^1.20.1", "graphql-subscriptions": "^2.0.0", "graphql-tag": "^2.12.6", - "graphql-upload": "^16.0.2", - "graphql-voyager": "^2.0.0", + "graphql-upload": "^17.0.0", + "graphql-voyager": "^2.1.0", "graphql-ws": "^5.16.0", - "helmet": "^7.1.0", + "helmet": "^8.0.0", "i18n": "^0.15.1", "image-hash": "^5.3.1", "ioredis": "^5.4.1", @@ -87,59 +91,68 @@ "mongoose": "^8.3.2", "mongoose-paginate-v2": "^1.8.3", "morgan": "^1.10.0", + "multer": "^1.4.5-lts.1", "nanoid": "^5.0.7", - "nodemailer": "^6.9.14", + "nodemailer": "^6.9.16", "pm2": "^5.4.0", "redis": "^4.7.0", "rrule": "^2.8.1", - "typedoc-plugin-markdown": "^4.2.3", - "uuid": "^10.0.0", + "typedoc-plugin-markdown": "^4.2.7", + "uuid": "^11.0.1", "validator": "^13.12.0", - "winston": "^3.14.2", + "winston": "^3.15.0", "ws": "^8.18.0", "yargs": "^17.7.2", "zod": "^3.23.8", "zod-error": "^1.5.0" }, "devDependencies": { - "@graphql-codegen/cli": "^5.0.2", - "@graphql-codegen/typescript": "^4.0.9", + "@eslint/compat": "^1.1.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "^8.57.0", + "@graphql-codegen/cli": "^5.0.3", + "@graphql-codegen/typescript": "^4.1.0", "@graphql-codegen/typescript-resolvers": "^4.2.1", "@graphql-eslint/eslint-plugin": "^3.20.1", "@parcel/watcher": "^2.4.1", "@types/bcryptjs": "^2.4.6", - "@types/cls-hooked": "^4.3.8", + "@types/cls-hooked": "^4.3.9", "@types/copy-paste": "^1.1.30", "@types/cors": "^2.8.17", "@types/express": "^4.17.17", - "@types/express-rate-limit": "^6.0.0", + "@types/express-rate-limit": "^6.0.2", "@types/graphql-depth-limit": "^1.1.6", "@types/i18n": "^0.13.12", "@types/inquirer": "^9.0.7", - "@types/jsonwebtoken": "^9.0.5", - "@types/lodash": "^4.17.7", + "@types/jsonwebtoken": "^9.0.7", + "@types/lodash": "^4.17.13", "@types/mongoose-paginate-v2": "^1.6.5", "@types/morgan": "^1.9.9", - "@types/node": "^22.5.2", - "@types/nodemailer": "^6.4.15", + "@types/multer": "^1.4.12", + "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.16", + "@types/supertest": "^6.0.2", "@types/uuid": "^10.0.0", - "@types/validator": "^13.12.0", - "@vitest/coverage-v8": "^2.0.5", + "@types/validator": "^13.12.2", + "@vitest/coverage-v8": "^2.1.5", "cls-bluebird": "^2.1.0", - "concurrently": "^8.2.2", - "eslint": "^8.56.0", + "concurrently": "^9.0.1", + "eslint": "^9.10.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-tsdoc": "^0.3.0", "get-graphql-schema": "^2.1.2", - "graphql-markdown": "^7.0.0", - "husky": "^9.1.5", + "globals": "^15.12.0", + "graphql-markdown": "^7.3.0", + "husky": "^9.1.6", "lint-staged": "^15.2.10", + "nodemon": "^3.1.7", "prettier": "^3.3.3", "rimraf": "^6.0.1", - "tsx": "^4.19.0", + "supertest": "^7.0.0", + "tsx": "^4.19.1", "typescript": "^5.5.4", - "vitest": "^2.0.5" + "vitest": "^2.1.3" }, "overrides": { "graphql-voyager": { diff --git a/public/markdown/images/minio-create-bucket.png b/public/markdown/images/minio-create-bucket.png new file mode 100644 index 00000000000..ed2278ee1bf Binary files /dev/null and b/public/markdown/images/minio-create-bucket.png differ diff --git a/public/markdown/images/mino-webui-login.png b/public/markdown/images/mino-webui-login.png new file mode 100644 index 00000000000..9864a3a13db Binary files /dev/null and b/public/markdown/images/mino-webui-login.png differ diff --git a/public/markdown/images/recaptcha_set_up.png b/public/markdown/images/recaptcha_set_up.png new file mode 100644 index 00000000000..5e9d6a0edaf Binary files /dev/null and b/public/markdown/images/recaptcha_set_up.png differ diff --git a/public/markdown/images/recaptcha_set_up.webp b/public/markdown/images/recaptcha_set_up.webp deleted file mode 100644 index 1a59f301b1e..00000000000 Binary files a/public/markdown/images/recaptcha_set_up.webp and /dev/null differ diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000000..ccbd3b8344d --- /dev/null +++ b/renovate.json @@ -0,0 +1,17 @@ +{ + "extends": ["config:base"], + "baseBranches": ["develop"], + "schedule": ["every weekend"], + "packageRules": [ + { + "packagePatterns": ["*"], + "groupName": "All Minor Updates", + "group": true, + "commitMessageAction": "Update dependencies (renov): " + } + ], + "labels": ["dependencies"], + "branchPrefix": "renovate/", + "automergeType": "pr", + "automerge": true +} diff --git a/requirements.txt b/requirements.txt index 9e99bba807c..ecfd2552ef4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -black==22.12.0 +black==24.3.0 pydocstyle==6.3.0 pylint==2.15.10 pymongo==4.3.3 diff --git a/sample_data/venue.json b/sample_data/venue.json new file mode 100644 index 00000000000..a2b44acb889 --- /dev/null +++ b/sample_data/venue.json @@ -0,0 +1,34 @@ +[ + { + "_id": "6437904485008f171cf29925", + "name": "Unity Foundation", + "description": "A large hall for community events in the Bronx.", + "capacity": 500, + "imageUrl": "https://example.com/bronx-hall.jpg", + "organization": "6437904485008f171cf29924" + }, + { + "_id": "6537904485008f171cf29925", + "name": "Unity Conference Room - Queens", + "description": "A small conference room for meetings in Queens.", + "capacity": 50, + "imageUrl": "https://example.com/queens-conference-room.jpg", + "organization": "6537904485008f171cf29924" + }, + { + "_id": "6637904485008f171cf29925", + "name": "Unity Arena - Staten Island", + "description": "A large outdoor arena for public events in Staten Island.", + "capacity": 2000, + "imageUrl": "https://example.com/staten-island-arena.jpg", + "organization": "6637904485008f171cf29924" + }, + { + "_id": "6737904485008f171cf29925", + "name": "Unity Hall - Brooklyn", + "description": "A community hall in Brooklyn for social events.", + "capacity": 300, + "imageUrl": "https://example.com/brooklyn-hall.jpg", + "organization": "6737904485008f171cf29924" + } +] diff --git a/schema.graphql b/schema.graphql index 72f34f12b21..a28cc18bf9d 100644 --- a/schema.graphql +++ b/schema.graphql @@ -5,8 +5,11 @@ directive @role(requires: UserType) on FIELD_DEFINITION type ActionItem { _id: ID! actionItemCategory: ActionItemCategory - allotedHours: Float - assignee: User + allottedHours: Float + assignee: EventVolunteer + assigneeGroup: EventVolunteerGroup + assigneeType: String! + assigneeUser: User assigner: User assignmentDate: Date! completionDate: Date! @@ -41,6 +44,7 @@ input ActionItemWhereInput { categoryName: String event_id: ID is_completed: Boolean + orgId: ID } enum ActionItemsOrderByInput { @@ -50,6 +54,11 @@ enum ActionItemsOrderByInput { dueDate_DESC } +input AddPeopleToUserTagInput { + tagId: ID! + userIds: [ID!]! +} + type Address { city: String countryCode: String @@ -190,6 +199,32 @@ input CampaignWhereInput { organizationId: ID } +type Chat { + _id: ID! + admins: [User] + createdAt: DateTime! + creator: User + image: String + isGroup: Boolean! + lastMessageId: String + messages: [ChatMessage] + name: String + organization: Organization + updatedAt: DateTime! + users: [User!]! +} + +type ChatMessage { + _id: ID! + chatMessageBelongsTo: Chat! + createdAt: DateTime! + deletedBy: [User] + messageContent: String! + replyTo: ChatMessage + sender: User! + updatedAt: DateTime! +} + type CheckIn { _id: ID! createdAt: DateTime! @@ -239,6 +274,7 @@ type Community { logoUrl: String name: String! socialMediaUrls: SocialMediaUrls + timeout: Int websiteLink: String } @@ -278,8 +314,9 @@ interface ConnectionPageInfo { scalar CountryCode input CreateActionItemInput { - allotedHours: Float + allottedHours: Float assigneeId: ID! + assigneeType: String! dueDate: Date eventId: ID preCompletionNotes: String @@ -338,8 +375,6 @@ type CreateCommentPayload { userErrors: [CreateCommentError!]! } -union CreateDirectChatError = OrganizationNotFoundError | UserNotFoundError - union CreateMemberError = MemberNotFoundError | OrganizationNotFoundError | UserNotAuthorizedAdminError | UserNotAuthorizedError | UserNotFoundError type CreateMemberPayload { @@ -549,26 +584,6 @@ type DeletePayload { success: Boolean! } -type DirectChat { - _id: ID! - createdAt: DateTime! - creator: User - messages: [DirectChatMessage] - organization: Organization - updatedAt: DateTime! - users: [User!]! -} - -type DirectChatMessage { - _id: ID! - createdAt: DateTime! - directChatMessageBelongsTo: DirectChat! - messageContent: String! - receiver: User! - sender: User! - updatedAt: DateTime! -} - type Donation { _id: ID! amount: Float! @@ -665,6 +680,8 @@ type Event { startTime: Time title: String! updatedAt: DateTime! + volunteerGroups: [EventVolunteerGroup] + volunteers: [EventVolunteer] } type EventAttendee { @@ -729,21 +746,25 @@ enum EventOrderByInput { type EventVolunteer { _id: ID! + assignments: [ActionItem] createdAt: DateTime! creator: User event: Event - group: EventVolunteerGroup - isAssigned: Boolean - isInvited: Boolean - response: String + groups: [EventVolunteerGroup] + hasAccepted: Boolean! + hoursHistory: [HoursHistory] + hoursVolunteered: Float! + isPublic: Boolean! updatedAt: DateTime! user: User! } type EventVolunteerGroup { _id: ID! + assignments: [ActionItem] createdAt: DateTime! creator: User + description: String event: Event leader: User! name: String @@ -753,20 +774,32 @@ type EventVolunteerGroup { } input EventVolunteerGroupInput { + description: String eventId: ID! - name: String + leaderId: ID! + name: String! + volunteerUserIds: [ID!]! volunteersRequired: Int } +enum EventVolunteerGroupOrderByInput { + assignments_ASC + assignments_DESC + volunteers_ASC + volunteers_DESC +} + input EventVolunteerGroupWhereInput { eventId: ID + leaderName: String name_contains: String - volunteerId: ID + orgId: ID + userId: ID } input EventVolunteerInput { eventId: ID! - groupId: ID! + groupId: ID userId: ID! } @@ -775,6 +808,19 @@ enum EventVolunteerResponse { YES } +input EventVolunteerWhereInput { + eventId: ID + groupId: ID + hasAccepted: Boolean + id: ID + name_contains: String +} + +enum EventVolunteersOrderByInput { + hoursVolunteered_ASC + hoursVolunteered_DESC +} + input EventWhereInput { description: String description_contains: String @@ -828,6 +874,35 @@ interface FieldError { path: [String!]! } +type File { + _id: ID! + archived: Boolean! + archivedAt: DateTime + backupStatus: String! + createdAt: DateTime! + encryption: Boolean! + fileName: String! + hash: Hash! + metadata: FileMetadata! + mimeType: String! + referenceCount: Int! + size: Int! + status: Status! + updatedAt: DateTime! + uri: String! + visibility: FileVisibility! +} + +type FileMetadata { + bucketName: String! + objectKey: String! +} + +enum FileVisibility { + PRIVATE + PUBLIC +} + input ForgotPasswordData { newPassword: String! otpToken: String! @@ -932,24 +1007,14 @@ type Group { updatedAt: DateTime! } -type GroupChat { - _id: ID! - createdAt: DateTime! - creator: User - messages: [GroupChatMessage] - organization: Organization! - title: String! - updatedAt: DateTime! - users: [User!]! +type Hash { + algorithm: String! + value: String! } -type GroupChatMessage { - _id: ID! - createdAt: DateTime! - groupChatMessageBelongsTo: GroupChat! - messageContent: String! - sender: User! - updatedAt: DateTime! +type HoursHistory { + date: Date! + hours: Float! } type InvalidCursor implements FieldError { @@ -1025,6 +1090,10 @@ type MembershipRequest { } input MembershipRequestsWhereInput { + creatorId: ID + creatorId_in: [ID!] + creatorId_not: ID + creatorId_not_in: [ID!] id: ID id_contains: ID id_in: [ID!] @@ -1044,21 +1113,6 @@ type Message { videoUrl: URL } -type MessageChat { - _id: ID! - createdAt: DateTime! - languageBarrier: Boolean - message: String! - receiver: User! - sender: User! - updatedAt: DateTime! -} - -input MessageChatInput { - message: String! - receiver: ID! -} - type MinimumLengthError implements FieldError { limit: Int! message: String! @@ -1077,12 +1131,12 @@ type Mutation { addLanguageTranslation(data: LanguageInput!): Language! addOrganizationCustomField(name: String!, organizationId: ID!, type: String!): OrganizationCustomField! addOrganizationImage(file: String!, organizationId: String!): Organization! + addPeopleToUserTag(input: AddPeopleToUserTagInput!): UserTag addPledgeToFundraisingCampaign(campaignId: ID!, pledgeId: ID!): FundraisingCampaignPledge! addUserCustomData(dataName: String!, dataValue: Any!, organizationId: ID!): UserCustomData! addUserImage(file: String!): User! - addUserToGroupChat(chatId: ID!, userId: ID!): GroupChat! addUserToUserFamily(familyId: ID!, userId: ID!): UserFamily! - adminRemoveGroup(groupId: ID!): GroupChat! + assignToUserTags(input: TagActionsInput!): UserTag assignUserTag(input: ToggleUserTagAssignInput!): User blockPluginCreationBySuperadmin(blockUser: Boolean!, userId: ID!): AppUserProfile! blockUser(organizationId: ID!, userId: ID!): User! @@ -1096,8 +1150,8 @@ type Mutation { createAgendaCategory(input: CreateAgendaCategoryInput!): AgendaCategory! createAgendaItem(input: CreateAgendaItemInput!): AgendaItem! createAgendaSection(input: CreateAgendaSectionInput!): AgendaSection! + createChat(data: chatInput!): Chat createComment(data: CommentInput!, postId: ID!): Comment - createDirectChat(data: createChatInput!): DirectChat! createDonation(amount: Float!, nameOfOrg: String!, nameOfUser: String!, orgId: ID!, payPalId: ID!, userId: ID!): Donation! createEvent(data: EventInput!, recurrenceRuleData: RecurrenceRuleInput): Event! createEventVolunteer(data: EventVolunteerInput!): EventVolunteer! @@ -1105,9 +1159,7 @@ type Mutation { createFund(data: FundInput!): Fund! createFundraisingCampaign(data: FundCampaignInput!): FundraisingCampaign! createFundraisingCampaignPledge(data: FundCampaignPledgeInput!): FundraisingCampaignPledge! - createGroupChat(data: createGroupChatInput!): GroupChat! createMember(input: UserAndOrganizationInput!): CreateMemberPayload! - createMessageChat(data: MessageChatInput!): MessageChat! createNote(data: NoteInput!): Note! createOrganization(data: OrganizationInput, file: String): Organization! createPlugin(pluginCreatedBy: String!, pluginDesc: String!, pluginName: String!, uninstalledOrgs: [ID!]): Plugin! @@ -1116,6 +1168,7 @@ type Mutation { createUserFamily(data: createUserFamilyInput!): UserFamily! createUserTag(input: CreateUserTagInput!): UserTag createVenue(data: VenueInput!): Venue + createVolunteerMembership(data: VolunteerMembershipInput!): VolunteerMembership! deleteAdvertisement(id: ID!): DeleteAdvertisementPayload deleteAgendaCategory(id: ID!): ID! deleteDonationById(id: ID!): DeletePayload! @@ -1142,13 +1195,12 @@ type Mutation { removeAgendaItem(id: ID!): AgendaItem! removeAgendaSection(id: ID!): ID! removeComment(id: ID!): Comment - removeDirectChat(chatId: ID!, organizationId: ID!): DirectChat! removeEvent(id: ID!, recurringEventDeleteType: RecurringEventMutationType): Event! removeEventAttendee(data: EventAttendeeInput!): User! removeEventVolunteer(id: ID!): EventVolunteer! removeEventVolunteerGroup(id: ID!): EventVolunteerGroup! + removeFromUserTags(input: TagActionsInput!): UserTag removeFundraisingCampaignPledge(id: ID!): FundraisingCampaignPledge! - removeGroupChat(chatId: ID!): GroupChat! removeMember(data: UserAndOrganizationInput!): Organization! removeOrganization(id: ID!): UserData! removeOrganizationCustomField(customFieldId: ID!, organizationId: ID!): OrganizationCustomField! @@ -1157,7 +1209,6 @@ type Mutation { removeSampleOrganization: Boolean! removeUserCustomData(organizationId: ID!): UserCustomData! removeUserFamily(familyId: ID!): UserFamily! - removeUserFromGroupChat(chatId: ID!, userId: ID!): GroupChat! removeUserFromUserFamily(familyId: ID!, userId: ID!): UserFamily! removeUserImage: User! removeUserTag(id: ID!): UserTag @@ -1165,8 +1216,7 @@ type Mutation { revokeRefreshTokenForUser: Boolean! saveFcmToken(token: String): Boolean! sendMembershipRequest(organizationId: ID!): MembershipRequest! - sendMessageToDirectChat(chatId: ID!, messageContent: String!): DirectChatMessage! - sendMessageToGroupChat(chatId: ID!, messageContent: String!): GroupChatMessage! + sendMessageToChat(chatId: ID!, messageContent: String!, replyTo: ID): ChatMessage! signUp(data: UserInput!, file: String): AuthData! togglePostPin(id: ID!, title: String): Post! unassignUserTag(input: ToggleUserTagAssignInput!): User @@ -1183,7 +1233,7 @@ type Mutation { updateCommunity(data: UpdateCommunityInput!): Boolean! updateEvent(data: UpdateEventInput!, id: ID!, recurrenceRuleData: RecurrenceRuleInput, recurringEventUpdateType: RecurringEventMutationType): Event! updateEventVolunteer(data: UpdateEventVolunteerInput, id: ID!): EventVolunteer! - updateEventVolunteerGroup(data: UpdateEventVolunteerGroupInput, id: ID!): EventVolunteerGroup! + updateEventVolunteerGroup(data: UpdateEventVolunteerGroupInput!, id: ID!): EventVolunteerGroup! updateFund(data: UpdateFundInput!, id: ID!): Fund! updateFundraisingCampaign(data: UpdateFundCampaignInput!, id: ID!): FundraisingCampaign! updateFundraisingCampaignPledge(data: UpdateFundCampaignPledgeInput!, id: ID!): FundraisingCampaignPledge! @@ -1192,10 +1242,12 @@ type Mutation { updateOrganization(data: UpdateOrganizationInput, file: String, id: ID!): Organization! updatePluginStatus(id: ID!, orgId: ID!): Plugin! updatePost(data: PostUpdateInput, id: ID!): Post! + updateSessionTimeout(timeout: Int!): Boolean! updateUserPassword(data: UpdateUserPasswordInput!): UserData! updateUserProfile(data: UpdateUserInput, file: String): User! updateUserRoleInOrganization(organizationId: ID!, role: String!, userId: ID!): Organization! updateUserTag(input: UpdateUserTagInput!): UserTag + updateVolunteerMembership(id: ID!, status: String!): VolunteerMembership! } type Note { @@ -1239,7 +1291,7 @@ type Organization { posts(after: String, before: String, first: PositiveInt, last: PositiveInt): PostsConnection updatedAt: DateTime! userRegistrationRequired: Boolean! - userTags(after: String, before: String, first: PositiveInt, last: PositiveInt): UserTagsConnection + userTags(after: String, before: String, first: PositiveInt, last: PositiveInt, sortedBy: UserTagSortedByInput, where: UserTagWhereInput): UserTagsConnection venues: [Venue] visibleInSearch: Boolean! } @@ -1400,7 +1452,7 @@ type Post { comments: [Comment] createdAt: DateTime! creator: User - imageUrl: URL + file: File likeCount: Int likedBy: [User] organization: Organization! @@ -1408,7 +1460,6 @@ type Post { text: String! title: String updatedAt: DateTime! - videoUrl: URL } type PostEdge { @@ -1487,22 +1538,22 @@ type Query { actionItemCategoriesByOrganization(orderBy: ActionItemsOrderByInput, organizationId: ID!, where: ActionItemCategoryWhereInput): [ActionItemCategory] actionItemsByEvent(eventId: ID!): [ActionItem] actionItemsByOrganization(eventId: ID, orderBy: ActionItemsOrderByInput, organizationId: ID!, where: ActionItemWhereInput): [ActionItem] + actionItemsByUser(orderBy: ActionItemsOrderByInput, userId: ID!, where: ActionItemWhereInput): [ActionItem] adminPlugin(orgId: ID!): [Plugin] advertisementsConnection(after: String, before: String, first: PositiveInt, last: PositiveInt): AdvertisementsConnection agendaCategory(id: ID!): AgendaCategory! agendaItemByEvent(relatedEventId: ID!): [AgendaItem] agendaItemByOrganization(organizationId: ID!): [AgendaItem] agendaItemCategoriesByOrganization(organizationId: ID!): [AgendaCategory] + chatById(id: ID!): Chat! + chatsByUserId(id: ID!): [Chat] checkAuth: User! customDataByOrganization(organizationId: ID!): [UserCustomData!]! customFieldsByOrganization(id: ID!): [OrganizationCustomField] - directChatById(id: ID!): DirectChat - directChatsByUserID(id: ID!): [DirectChat] - directChatsMessagesByChatID(id: ID!): [DirectChatMessage] event(id: ID!): Event - eventVolunteersByEvent(id: ID!): [EventVolunteer] + eventsAttendedByUser(id: ID, orderBy: EventOrderByInput): [Event] eventsByOrganization(id: ID, orderBy: EventOrderByInput): [Event] - eventsByOrganizationConnection(first: Int, orderBy: EventOrderByInput, skip: Int, where: EventWhereInput): [Event!]! + eventsByOrganizationConnection(first: Int, orderBy: EventOrderByInput, skip: Int, upcomingOnly: Boolean, where: EventWhereInput): [Event!]! fundsByOrganization(orderBy: FundOrderByInput, organizationId: ID!, where: FundWhereInput): [Fund] getAgendaItem(id: ID!): AgendaItem getAgendaSection(id: ID!): AgendaSection @@ -1515,19 +1566,20 @@ type Query { getEventAttendee(eventId: ID!, userId: ID!): EventAttendee getEventAttendeesByEventId(eventId: ID!): [EventAttendee] getEventInvitesByUserId(userId: ID!): [EventAttendee!]! - getEventVolunteerGroups(where: EventVolunteerGroupWhereInput): [EventVolunteerGroup]! + getEventVolunteerGroups(orderBy: EventVolunteerGroupOrderByInput, where: EventVolunteerGroupWhereInput!): [EventVolunteerGroup]! + getEventVolunteers(orderBy: EventVolunteersOrderByInput, where: EventVolunteerWhereInput!): [EventVolunteer]! getFundById(id: ID!, orderBy: CampaignOrderByInput, where: CampaignWhereInput): Fund! getFundraisingCampaignPledgeById(id: ID!): FundraisingCampaignPledge! getFundraisingCampaigns(campaignOrderby: CampaignOrderByInput, pledgeOrderBy: PledgeOrderByInput, where: CampaignWhereInput): [FundraisingCampaign]! getNoteById(id: ID!): Note! getPledgesByUserId(orderBy: PledgeOrderByInput, userId: ID!, where: PledgeWhereInput): [FundraisingCampaignPledge] getPlugins: [Plugin] + getRecurringEvents(baseRecurringEventId: ID!): [Event] getUserTag(id: ID!): UserTag - getUserTagAncestors(id: ID!): [UserTag] getVenueByOrgId(first: Int, orderBy: VenueOrderByInput, orgId: ID!, skip: Int, where: VenueWhereInput): [Venue] + getVolunteerMembership(orderBy: VolunteerMembershipOrderByInput, where: VolunteerMembershipWhereInput!): [VolunteerMembership]! + getVolunteerRanks(orgId: ID!, where: VolunteerRankWhereInput!): [VolunteerRank]! getlanguage(lang_code: String!): [Translation] - groupChatById(id: ID!): GroupChat - groupChatsByUserId(id: ID!): [GroupChat] hasSubmittedFeedback(eventId: ID!, userId: ID!): Boolean isSampleOrganization(id: ID!): Boolean! joinedOrganizations(id: ID): [Organization] @@ -1628,12 +1680,15 @@ enum Status { } type Subscription { - directMessageChat: MessageChat - messageSentToDirectChat(userId: ID!): DirectChatMessage - messageSentToGroupChat(userId: ID!): GroupChatMessage + messageSentToChat(userId: ID!): ChatMessage onPluginUpdate: Plugin } +input TagActionsInput { + currentTagId: ID! + selectedTagIds: [ID!]! +} + scalar Time input ToggleUserTagAssignInput { @@ -1669,8 +1724,9 @@ input UpdateActionItemCategoryInput { } input UpdateActionItemInput { - allotedHours: Float + allottedHours: Float assigneeId: ID + assigneeType: String completionDate: Date dueDate: Date isCompleted: Boolean @@ -1740,16 +1796,16 @@ input UpdateEventInput { } input UpdateEventVolunteerGroupInput { - eventId: ID + description: String + eventId: ID! name: String volunteersRequired: Int } input UpdateEventVolunteerInput { - eventId: ID - isAssigned: Boolean - isInvited: Boolean - response: EventVolunteerResponse + assignments: [ID] + hasAccepted: Boolean + isPublic: Boolean } input UpdateFundCampaignInput { @@ -1827,8 +1883,11 @@ type User { email: EmailAddress! employmentStatus: EmploymentStatus eventAdmin: [Event] + eventsAttended: [Event] + file: File firstName: String! gender: Gender + identifier: Int! image: String joinedOrganizations: [Organization] lastName: String! @@ -1883,6 +1942,10 @@ input UserInput { selectedOrganization: ID! } +input UserNameWhereInput { + starts_with: String! +} + type UserNotAuthorizedAdminError implements Error { message: String! } @@ -1924,11 +1987,14 @@ type UserTag { """A field to get the mongodb object id identifier for this UserTag.""" _id: ID! + """A field to traverse the ancestor tags of this UserTag.""" + ancestorTags: [UserTag] + """ A connection field to traverse a list of UserTag this UserTag is a parent to. """ - childTags(after: String, before: String, first: PositiveInt, last: PositiveInt): UserTagsConnection + childTags(after: String, before: String, first: PositiveInt, last: PositiveInt, sortedBy: UserTagSortedByInput, where: UserTagWhereInput): UserTagsConnection """A field to get the name of this UserTag.""" name: String! @@ -1943,7 +2009,39 @@ type UserTag { A connection field to traverse a list of User this UserTag is assigned to. """ - usersAssignedTo(after: String, before: String, first: PositiveInt, last: PositiveInt): UsersConnection + usersAssignedTo(after: String, before: String, first: PositiveInt, last: PositiveInt, sortedBy: UserTagUsersAssignedToSortedByInput, where: UserTagUsersAssignedToWhereInput): UsersConnection + + """ + A connection field to traverse a list of Users this UserTag is not assigned + to, to see and select among them and assign this tag. + """ + usersToAssignTo(after: String, before: String, first: PositiveInt, last: PositiveInt, where: UserTagUsersToAssignToWhereInput): UsersConnection +} + +input UserTagNameWhereInput { + starts_with: String! +} + +input UserTagSortedByInput { + id: SortedByOrder! +} + +input UserTagUsersAssignedToSortedByInput { + id: SortedByOrder! +} + +input UserTagUsersAssignedToWhereInput { + firstName: UserNameWhereInput + lastName: UserNameWhereInput +} + +input UserTagUsersToAssignToWhereInput { + firstName: UserNameWhereInput + lastName: UserNameWhereInput +} + +input UserTagWhereInput { + name: UserTagNameWhereInput } """A default connection on the UserTag type.""" @@ -2036,6 +2134,53 @@ input VenueWhereInput { name_starts_with: String } +type VolunteerMembership { + _id: ID! + createdAt: DateTime! + createdBy: User + event: Event! + group: EventVolunteerGroup + status: String! + updatedAt: DateTime! + updatedBy: User + volunteer: EventVolunteer! +} + +input VolunteerMembershipInput { + event: ID! + group: ID + status: String! + userId: ID! +} + +enum VolunteerMembershipOrderByInput { + createdAt_ASC + createdAt_DESC +} + +input VolunteerMembershipWhereInput { + eventId: ID + eventTitle: String + filter: String + groupId: ID + status: String + userId: ID + userName: String +} + +type VolunteerRank { + hoursVolunteered: Float! + rank: Int! + user: User! +} + +input VolunteerRankWhereInput { + limit: Int + nameContains: String + orderBy: String! + timeFrame: String! +} + enum WeekDays { FRIDAY MONDAY @@ -2046,22 +2191,14 @@ enum WeekDays { WEDNESDAY } -input createChatInput { +input chatInput { + image: String + isGroup: Boolean! + name: String organizationId: ID userIds: [ID!]! } -type createDirectChatPayload { - directChat: DirectChat - userErrors: [CreateDirectChatError!]! -} - -input createGroupChatInput { - organizationId: ID! - title: String! - userIds: [ID!]! -} - input createUserFamilyInput { title: String! userIds: [ID!]! diff --git a/scripts/cloud-api-demo/create_env.py b/scripts/cloud-api-demo/create_env.py index 9c3c2e23cf4..4dd571fbb76 100644 --- a/scripts/cloud-api-demo/create_env.py +++ b/scripts/cloud-api-demo/create_env.py @@ -30,6 +30,12 @@ def main(): required=True, help="Last resort superadmin email", ) + parser.add_argument( + "--minio_root_user", required=True, help="Minio root user" + ) + parser.add_argument( + "--minio_root_password", required=True, help="Minio root password" + ) # Parse the command line arguments args = parser.parse_args() @@ -56,6 +62,9 @@ def main(): REDIS_PORT=6379 REDIS_PASSWORD= TALAWA_ADMIN_URL=api-demo.talawa.io + MINIO_ROOT_USER={args.minio_root_user} + MINIO_ROOT_PASSWORD={args.minio_root_password} + MINIO_ENDPOINT= """ # Write the .env file diff --git a/setup.ts b/setup.ts index 98571be51cd..b8ad43ecc34 100644 --- a/setup.ts +++ b/setup.ts @@ -1,12 +1,11 @@ -// eslint-disable-next-line +/* eslint-disable no-restricted-imports */ import * as cryptolib from "crypto"; import dotenv from "dotenv"; import fs from "fs"; import inquirer from "inquirer"; import path from "path"; -/* eslint-disable */ import type { ExecException } from "child_process"; -import { exec } from "child_process"; +import { exec, spawn } from "child_process"; import { MongoClient } from "mongodb"; import { MAXIMUM_IMAGE_SIZE_LIMIT_KB } from "./src/constants"; import { @@ -31,7 +30,8 @@ import { askForSuperAdminEmail } from "./src/setup/superAdmin"; import { updateEnvVariable } from "./src/setup/updateEnvVariable"; import { verifySmtpConnection } from "./src/setup/verifySmtpConnection"; import { loadDefaultOrganiation } from "./src/utilities/loadDefaultOrg"; -/* eslint-enable */ +import { isMinioInstalled } from "./src/setup/isMinioInstalled"; +import { installMinio } from "./src/setup/installMinio"; dotenv.config(); @@ -169,10 +169,10 @@ function transactionLogPath(logPath: string | null): void { } async function askForTransactionLogPath(): Promise { - let logPath: string | null; - // Keep asking for path, until user gives a valid path - // eslint-disable-next-line no-constant-condition - while (true) { + let logPath: string | null = null; + let isValidPath = false; + + while (!isValidPath) { const response = await inquirer.prompt([ { type: "input", @@ -182,10 +182,11 @@ async function askForTransactionLogPath(): Promise { }, ]); logPath = response.logPath; + if (logPath && fs.existsSync(logPath)) { try { fs.accessSync(logPath, fs.constants.R_OK | fs.constants.W_OK); - break; + isValidPath = true; } catch { console.error( "The file is not readable/writable. Please enter a valid file path.", @@ -197,7 +198,8 @@ async function askForTransactionLogPath(): Promise { ); } } - return logPath; + + return logPath as string; } //Wipes the existing data in the database @@ -219,7 +221,7 @@ export async function wipeExistingData(url: string): Promise { } console.log("All existing data has been deleted."); } - } catch (error) { + } catch { console.error("Could not connect to database to check for data"); } client.close(); @@ -246,7 +248,7 @@ export async function checkDb(url: string): Promise { } else { dbEmpty = true; } - } catch (error) { + } catch { console.error("Could not connect to database to check for data"); } client.close(); @@ -460,6 +462,63 @@ export async function mongoDB(): Promise { } } +/* + For Docker setup +*/ + +async function runDockerComposeWithLogs(): Promise { + // Check if Docker daemon is running + try { + await new Promise((resolve, reject) => { + const dockerCheck = spawn( + process.platform === "win32" ? "docker.exe" : "docker", + ["info"], + { stdio: "ignore" }, + ); + dockerCheck.on("error", reject); + dockerCheck.on("close", (code) => + code === 0 + ? resolve(null) + : reject(new Error("Docker daemon not running")), + ); + }); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error( + `Docker daemon is not running. Please start Docker and try again. Details: ${errorMessage}`, + ); + } + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + dockerCompose.kill(); + reject(new Error("Docker compose operation timed out after 5 minutes")); + }, 300000); + + const dockerCompose = spawn( + process.platform === "win32" ? "docker-compose.exe" : "docker-compose", + ["-f", "docker-compose.dev.yaml", "up", "--build", "-d"], + { stdio: "inherit" }, + ); + + dockerCompose.on("error", (error) => { + clearTimeout(timeout); + console.error("Error running docker-compose:", error); + reject(error); + }); + + dockerCompose.on("close", (code) => { + clearTimeout(timeout); + if (code === 0) { + console.log("Docker Compose completed successfully."); + resolve(); + } else { + reject(new Error(`Docker Compose exited with code ${code}`)); + } + }); + }); +} + //Get recaptcha details /** * The function `recaptcha` prompts the user to enter a reCAPTCHA secret key, validates the input, and @@ -672,6 +731,157 @@ export async function configureSmtp(): Promise { console.log("SMTP configuration saved successfully."); } +/** + * Configures MinIO settings, including installation check, data directory, and credentials. + * + * This function performs the following steps: + * 1. Checks if MinIO is installed (for non-Docker installations) + * 2. Prompts for MinIO installation if not found + * 3. Checks for existing MinIO data directory configuration + * 4. Allows user to change the data directory if desired + * 5. Prompts for MinIO root user, password, and bucket name + * 6. Updates the environment variables with the new configuration + * + * @param isDockerInstallation - A boolean indicating whether the setup is for a Docker installation. + * @throws Will throw an error if there are issues with file operations or user input validation. + * @returns A Promise that resolves when the configuration is complete. + */ +export async function configureMinio( + isDockerInstallation: boolean, +): Promise { + if (!isDockerInstallation) { + console.log("Checking MinIO installation..."); + if (isMinioInstalled()) { + console.log("MinIO is already installed."); + } else { + console.log("MinIO is not installed on your system."); + const { installMinioNow } = await inquirer.prompt([ + { + type: "confirm", + name: "installMinioNow", + message: "Would you like to install MinIO now?", + default: true, + }, + ]); + if (installMinioNow) { + console.log("Installing MinIO..."); + try { + await installMinio(); + console.log("Successfully installed MinIO on your system."); + } catch (err) { + console.error(err); + return; + } + } else { + console.log( + "MinIO installation skipped. Please install MinIO manually before proceeding.", + ); + return; + } + } + } + + const envFile = process.env.NODE_ENV === "test" ? ".env_test" : ".env"; + const config = dotenv.parse(fs.readFileSync(envFile)); + + const currentDataDir = config.MINIO_DATA_DIR || process.env.MINIO_DATA_DIR; + let changeDataDir = false; + + if (currentDataDir) { + console.log( + `[MINIO] Existing MinIO data directory found: ${currentDataDir}`, + ); + const { confirmChange } = await inquirer.prompt([ + { + type: "confirm", + name: "confirmChange", + message: + "Do you want to change the MinIO data directory? (Warning: All existing data will be lost)", + default: false, + }, + ]); + changeDataDir = confirmChange; + } + + if (!currentDataDir || changeDataDir) { + const { MINIO_DATA_DIR } = await inquirer.prompt([ + { + type: "input", + name: "MINIO_DATA_DIR", + message: "Enter MinIO data directory (press Enter for default):", + default: "./data", + validate: (input: string): boolean | string => + input.trim() !== "" ? true : "MinIO data directory is required.", + }, + ]); + + if (changeDataDir && currentDataDir) { + try { + fs.rmSync(currentDataDir, { recursive: true, force: true }); + console.log( + `[MINIO] Removed existing data directory: ${currentDataDir}`, + ); + } catch (err) { + console.error(`[MINIO] Error removing existing data directory: ${err}`); + } + } + + config.MINIO_DATA_DIR = MINIO_DATA_DIR; + console.log(`[MINIO] MinIO data directory set to: ${MINIO_DATA_DIR}`); + + let fullPath = MINIO_DATA_DIR; + if (!path.isAbsolute(MINIO_DATA_DIR)) { + fullPath = path.join(process.cwd(), MINIO_DATA_DIR); + } + if (!fs.existsSync(fullPath)) { + fs.mkdirSync(fullPath, { recursive: true }); + } + } + + const minioConfig = await inquirer.prompt([ + { + type: "input", + name: "MINIO_ROOT_USER", + message: "Enter MinIO root user:", + default: + config.MINIO_ROOT_USER || process.env.MINIO_ROOT_USER || "talawa", + validate: (input: string): boolean | string => + input.trim() !== "" ? true : "MinIO root user is required.", + }, + { + type: "password", + name: "MINIO_ROOT_PASSWORD", + message: "Enter MinIO root password:", + default: + config.MINIO_ROOT_PASSWORD || + process.env.MINIO_ROOT_PASSWORD || + "talawa1234", + validate: (input: string): boolean | string => + input.trim() !== "" ? true : "MinIO root password is required.", + }, + { + type: "input", + name: "MINIO_BUCKET", + message: "Enter MinIO bucket name:", + default: config.MINIO_BUCKET || process.env.MINIO_BUCKET || "talawa", + validate: (input: string): boolean | string => + input.trim() !== "" ? true : "MinIO bucket name is required.", + }, + ]); + + const minioEndpoint = isDockerInstallation + ? "http://minio:9000" + : "http://localhost:9000"; + + config.MINIO_ENDPOINT = minioEndpoint; + config.MINIO_ROOT_USER = minioConfig.MINIO_ROOT_USER; + config.MINIO_ROOT_PASSWORD = minioConfig.MINIO_ROOT_PASSWORD; + config.MINIO_BUCKET = minioConfig.MINIO_BUCKET; + + updateEnvVariable(config); + console.log("[MINIO] MinIO configuration added successfully.\n"); +} + /** * The main function sets up the Talawa API by prompting the user to configure various environment * variables and import sample data if desired. @@ -706,7 +916,7 @@ async function main(): Promise { const { shouldGenerateAccessToken } = await inquirer.prompt({ type: "confirm", name: "shouldGenerateAccessToken", - message: "Would you like to generate a new access token secret?", + message: "Would you like us to auto-generate a new access token secret?", default: process.env.ACCESS_TOKEN_SECRET ? false : true, }); @@ -722,7 +932,8 @@ async function main(): Promise { const { shouldGenerateRefreshToken } = await inquirer.prompt({ type: "confirm", name: "shouldGenerateRefreshToken", - message: "Would you like to generate a new refresh token secret?", + message: + "Would you like to us to auto-generate a new refresh token secret?", default: process.env.REFRESH_TOKEN_SECRET ? false : true, }); @@ -763,7 +974,7 @@ async function main(): Promise { type: "confirm", name: "isDockerInstallation", message: "Are you setting up this project using Docker?", - default: false, + default: process.env.MONGO ? false : true, }); if (isDockerInstallation) { @@ -771,6 +982,7 @@ async function main(): Promise { const REDIS_HOST = "localhost"; const REDIS_PORT = "6379"; // default Redis port const REDIS_PASSWORD = ""; + const MINIO_ENDPOINT = "http://minio:9000"; const config = dotenv.parse(fs.readFileSync(".env")); @@ -778,16 +990,19 @@ async function main(): Promise { config.REDIS_HOST = REDIS_HOST; config.REDIS_PORT = REDIS_PORT; config.REDIS_PASSWORD = REDIS_PASSWORD; + config.MINIO_ENDPOINT = MINIO_ENDPOINT; process.env.MONGO_DB_URL = DB_URL; process.env.REDIS_HOST = REDIS_HOST; process.env.REDIS_PORT = REDIS_PORT; process.env.REDIS_PASSWORD = REDIS_PASSWORD; + process.env.MINIO_ENDPOINT = MINIO_ENDPOINT; updateEnvVariable(config); console.log(`Your MongoDB URL is:\n${process.env.MONGO_DB_URL}`); console.log(`Your Redis host is:\n${process.env.REDIS_HOST}`); console.log(`Your Redis port is:\n${process.env.REDIS_PORT}`); + console.log(`Your MinIO endpoint is:\n${process.env.MINIO_ENDPOINT}`); } if (!isDockerInstallation) { @@ -859,7 +1074,7 @@ async function main(): Promise { { type: "input", name: "serverPort", - message: "Enter the server port:", + message: "Enter the Talawa-API server port:", default: process.env.SERVER_PORT || 4000, }, ]); @@ -904,6 +1119,17 @@ async function main(): Promise { } } + console.log( + `\nConfiguring MinIO storage...\n` + + `${ + isDockerInstallation + ? `Since you are using Docker, MinIO will be configured with the Docker-specific endpoint: http://minio:9000.\n` + : `Since you are not using Docker, MinIO will be configured with the local endpoint: http://localhost:9000.\n` + }`, + ); + + await configureMinio(isDockerInstallation); + if (process.env.LAST_RESORT_SUPERADMIN_EMAIL) { console.log( `\nSuper Admin of last resort already exists with the value ${process.env.LAST_RESORT_SUPERADMIN_EMAIL}`, @@ -969,24 +1195,25 @@ async function main(): Promise { default: false, }); if (shouldOverwriteData) { + await wipeExistingData(process.env.MONGO_DB_URL); const { overwriteDefaultData } = await inquirer.prompt({ type: "confirm", name: "overwriteDefaultData", - message: "Do you want to import default data?", + message: + "Do you want to import the required default data to start using Talawa in a production environment?", default: false, }); if (overwriteDefaultData) { - await wipeExistingData(process.env.MONGO_DB_URL); await importDefaultData(); } else { const { overwriteSampleData } = await inquirer.prompt({ type: "confirm", name: "overwriteSampleData", - message: "Do you want to import sample data?", + message: + "Do you want to import Talawa sample data for testing and evaluation purposes?", default: false, }); if (overwriteSampleData) { - await wipeExistingData(process.env.MONGO_DB_URL); await importData(); } } @@ -995,9 +1222,11 @@ async function main(): Promise { const { shouldImportSampleData } = await inquirer.prompt({ type: "confirm", name: "shouldImportSampleData", - message: "Do you want to import Sample data?", + message: + "Do you want to import Talawa sample data for testing and evaluation purposes?", default: false, }); + await wipeExistingData(process.env.MONGO_DB_URL); if (shouldImportSampleData) { await importData(); } else { @@ -1009,6 +1238,64 @@ async function main(): Promise { console.log( "\nCongratulations! Talawa API has been successfully setup! 🥂🎉", ); + + const { shouldStartDockerContainers } = await inquirer.prompt({ + type: "confirm", + name: "shouldStartDockerContainers", + message: "Do you want to start the Docker containers now?", + default: true, + }); + + const { shouldImportSampleData } = await inquirer.prompt({ + type: "confirm", + name: "shouldImportSampleData", + message: + "Do you want to import Talawa sample data for testing and evaluation purposes?", + default: true, + }); + + if (isDockerInstallation) { + if (shouldStartDockerContainers) { + console.log("Starting docker container..."); + try { + await runDockerComposeWithLogs(); + console.log("Docker containers have been built successfully!"); + // Wait for mongoDB to be ready + console.log("Waiting for mongoDB to be ready..."); + let isConnected = false; + const maxRetries = 30; // 30 seconds timeout + let retryCount = 0; + while (!isConnected) { + if (retryCount >= maxRetries) { + throw new Error( + "Timed out waiting for MongoDB to be ready after 30 seconds", + ); + } + try { + const client = new MongoClient(process.env.MONGO_DB_URL as string); + await client.connect(); + await client.db().command({ ping: 1 }); + client.close(); + isConnected = true; + console.log("MongoDB is ready!"); + } catch (err) { + const error = err instanceof Error ? err.message : String(err); + console.log( + `Waiting for MongoDB to be ready... Retry ${retryCount + 1}/${maxRetries}. Details: ${error}`, + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); + retryCount++; + } + } + + if (shouldImportSampleData) { + await importData(); + } + } catch (err) { + console.log("Some error occurred: " + err); + } + } + } } main(); diff --git a/src/REST/controllers/mutation/createPost.ts b/src/REST/controllers/mutation/createPost.ts new file mode 100644 index 00000000000..4a59978317d --- /dev/null +++ b/src/REST/controllers/mutation/createPost.ts @@ -0,0 +1,262 @@ +import type { Response } from "express"; +import mongoose from "mongoose"; +import { + INTERNAL_SERVER_ERROR, + LENGTH_VALIDATION_ERROR, + ORGANIZATION_NOT_FOUND_ERROR, + PLEASE_PROVIDE_TITLE, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_AUTHORIZED_TO_PIN, + USER_NOT_FOUND_ERROR, + USER_NOT_MEMBER_FOR_ORGANIZATION, +} from "../../../constants"; +import { errors, requestContext } from "../../../libraries"; +import { isValidString } from "../../../libraries/validators/validateString"; +import type { + InterfaceAppUserProfile, + InterfaceOrganization, + InterfaceUser, +} from "../../../models"; +import { AppUserProfile, Organization, Post, User } from "../../../models"; +import { cacheAppUserProfile } from "../../../services/AppUserProfileCache/cacheAppUserProfile"; +import { findAppUserProfileCache } from "../../../services/AppUserProfileCache/findAppUserProfileCache"; +import { cacheOrganizations } from "../../../services/OrganizationCache/cacheOrganizations"; +import { findOrganizationsInCache } from "../../../services/OrganizationCache/findOrganizationsInCache"; +import { cachePosts } from "../../../services/PostCache/cachePosts"; +import { cacheUsers } from "../../../services/UserCache/cacheUser"; +import { findUserInCache } from "../../../services/UserCache/findUserInCache"; +import type { InterfaceAuthenticatedRequest } from "../../../middleware"; +import { uploadFile } from "../../services/file"; + +interface InterfaceCreatePostRequestBody { + organizationId: string; + title?: string; + text: string; + pinned?: boolean; +} + +/** + * Controller for creating posts within organizations + */ + +/** + * Creates a new post within an organization + * async + * function - createPost + * @param req - Express request object with authenticated user + * @param res - Express response object + * @throws NotFoundError - When user or organization is not found + * @throws UnauthorizedError - When user is not authorized or lacks permissions + * @throws InputValidationError - When title or text validation fails + * @returns Promise - Responds with created post or error + * + * Description + * This controller handles post creation with the following features: + * - Validates user membership in the organization + * - Supports file attachments + * - Handles post pinning with proper authorization + * - Validates title and text length + * - Caches created posts and updated organizations + * + * Request body expects: + * ```typescript + * { + * organizationId: string; + * title?: string; + * text: string; + * pinned?: boolean; + * } + * ``` + */ +export const createPost = async ( + req: InterfaceAuthenticatedRequest, + res: Response, +): Promise => { + const userId = req.userId; + const { + organizationId, + title, + text, + pinned, + }: InterfaceCreatePostRequestBody = req.body; + + try { + // Get the current user + let currentUser: InterfaceUser | null; + const userFoundInCache = await findUserInCache([userId as string]); + currentUser = userFoundInCache[0]; + if (currentUser === null) { + currentUser = await User.findOne({ _id: userId }).lean(); + if (currentUser !== null) { + await cacheUsers([currentUser]); + } + } + + // Check if currentUser exists + if (!currentUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + // Get current user's app profile + let currentUserAppProfile: InterfaceAppUserProfile | null; + const appUserProfileFoundInCache = await findAppUserProfileCache([ + currentUser.appUserProfileId?.toString(), + ]); + currentUserAppProfile = appUserProfileFoundInCache[0]; + if (currentUserAppProfile === null) { + currentUserAppProfile = await AppUserProfile.findOne({ + userId: currentUser._id, + }).lean(); + if (currentUserAppProfile !== null) { + await cacheAppUserProfile([currentUserAppProfile]); + } + } + + if (!currentUserAppProfile) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Get the organization + let organization: InterfaceOrganization | null; + const organizationFoundInCache = await findOrganizationsInCache([ + organizationId, + ]); + organization = organizationFoundInCache[0]; + if (organization === null) { + organization = await Organization.findOne({ _id: organizationId }).lean(); + if (organization) { + await cacheOrganizations([organization]); + } + } + + if (!organization) { + throw new errors.NotFoundError( + requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), + ORGANIZATION_NOT_FOUND_ERROR.CODE, + ORGANIZATION_NOT_FOUND_ERROR.PARAM, + ); + } + + // Check if user is a member of the organization or a super admin + const isSuperAdmin = currentUserAppProfile.isSuperAdmin; + const currentUserIsOrganizationMember = organization.members.some( + (memberId) => + new mongoose.Types.ObjectId(memberId?.toString()).equals(userId), + ); + + if (!currentUserIsOrganizationMember && !isSuperAdmin) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE), + USER_NOT_MEMBER_FOR_ORGANIZATION.CODE, + USER_NOT_MEMBER_FOR_ORGANIZATION.PARAM, + ); + } + + let fileUploadResponse; + if (req.file) { + fileUploadResponse = await uploadFile(req, res); + } + + // Validate title and pinned status + if (!title && pinned) { + throw new errors.InputValidationError( + requestContext.translate(PLEASE_PROVIDE_TITLE.MESSAGE), + PLEASE_PROVIDE_TITLE.CODE, + ); + } + + // Validate title and text length + if (title) { + const validationResultTitle = isValidString(title, 256); + if (!validationResultTitle.isLessThanMaxLength) { + throw new errors.InputValidationError( + requestContext.translate( + `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, + ), + LENGTH_VALIDATION_ERROR.CODE, + ); + } + } + + const validationResultText = isValidString(text, 500); + if (!validationResultText.isLessThanMaxLength) { + throw new errors.InputValidationError( + requestContext.translate( + `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in information`, + ), + LENGTH_VALIDATION_ERROR.CODE, + ); + } + + // Check permissions for pinning + if (pinned) { + const currentUserIsOrganizationAdmin = + currentUserAppProfile.adminFor.some((orgId) => + new mongoose.Types.ObjectId(orgId?.toString()).equals(organizationId), + ); + + if ( + !(currentUserAppProfile.isSuperAdmin || currentUserIsOrganizationAdmin) + ) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_TO_PIN.MESSAGE), + USER_NOT_AUTHORIZED_TO_PIN.CODE, + USER_NOT_AUTHORIZED_TO_PIN.PARAM, + ); + } + } + + // Create the post + const createdPost = await Post.create({ + title, + text, + pinned: pinned || false, + creatorId: userId, + organization: organizationId, + file: fileUploadResponse?._id, + }); + + if (createdPost !== null) { + await cachePosts([createdPost]); + } + + // Update organization if post is pinned + if (pinned) { + const updatedOrganization = await Organization.findOneAndUpdate( + { _id: organizationId }, + { + $push: { + pinnedPosts: createdPost._id, + }, + }, + { + new: true, + }, + ); + + await cacheOrganizations([updatedOrganization as InterfaceOrganization]); + } + + // Send response + res.status(201).json({ + post: createdPost, + }); + } catch (error) { + console.error(error); + if (error instanceof Error) { + res.status(500).json({ error: error.message }); + } else { + res.status(500).json({ + error: requestContext.translate(INTERNAL_SERVER_ERROR.MESSAGE), + }); + } + } +}; diff --git a/src/REST/controllers/mutation/index.ts b/src/REST/controllers/mutation/index.ts new file mode 100644 index 00000000000..d39f54f2c9c --- /dev/null +++ b/src/REST/controllers/mutation/index.ts @@ -0,0 +1,2 @@ +export * from "./createPost"; +export * from "./updatePost"; diff --git a/src/REST/controllers/mutation/updatePost.ts b/src/REST/controllers/mutation/updatePost.ts new file mode 100644 index 00000000000..400ff5e0846 --- /dev/null +++ b/src/REST/controllers/mutation/updatePost.ts @@ -0,0 +1,246 @@ +import type { Response } from "express"; +import { Types } from "mongoose"; +import { + INTERNAL_SERVER_ERROR, + LENGTH_VALIDATION_ERROR, + PLEASE_PROVIDE_TITLE, + POST_NEEDS_TO_BE_PINNED, + POST_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../../constants"; +import { errors, requestContext } from "../../../libraries"; +import { isValidString } from "../../../libraries/validators/validateString"; +import type { + InterfaceAppUserProfile, + InterfacePost, + InterfaceUser, +} from "../../../models"; +import { AppUserProfile, Post, User } from "../../../models"; +import { cachePosts } from "../../../services/PostCache/cachePosts"; +import { findPostsInCache } from "../../../services/PostCache/findPostsInCache"; +import { findUserInCache } from "../../../services/UserCache/findUserInCache"; +import { cacheUsers } from "../../../services/UserCache/cacheUser"; +import { findAppUserProfileCache } from "../../../services/AppUserProfileCache/findAppUserProfileCache"; +import { cacheAppUserProfile } from "../../../services/AppUserProfileCache/cacheAppUserProfile"; +import type { InterfaceAuthenticatedRequest } from "../../../middleware"; +import { deleteFile, uploadFile } from "../../services/file"; + +interface InterfaceUpdatePostRequestBody { + title?: string; + text?: string; + pinned?: boolean; +} + +/** + * Controller for updating existing posts within organizations + */ + +/** + * Updates an existing post + * async + * function - updatePost + * @param req - Express request object with authenticated user + * @param res - Express response object + * @throws NotFoundError - When user or post is not found + * @throws UnauthorizedError - When user lacks permissions to update the post + * @throws InputValidationError - When title/text validation fails or pinned status requirements aren't met + * @returns Promise - Responds with updated post or error + * + * Description + * This controller handles post updates with the following features: + * - Validates user permissions (creator, organization admin, or super admin) + * - Supports file attachment updates with cleanup of old files + * - Enforces business rules for pinned posts and titles + * - Validates content length restrictions + * - Maintains cache consistency + * + * Request body expects: + * ```typescript + * { + * title?: string; + * text?: string; + * pinned?: boolean; + * } + * ``` + * + * Authorization Rules: + * - Post creator can edit their own posts + * - Organization admins can edit posts in their organizations + * - Super admins can edit any post + */ + +export const updatePost = async ( + req: InterfaceAuthenticatedRequest, + res: Response, +): Promise => { + const userId = req.userId; + const postId = req.params.id; + const { title, text, pinned }: InterfaceUpdatePostRequestBody = req.body; + + try { + // Get the current user + let currentUser: InterfaceUser | null; + const userFoundInCache = await findUserInCache([userId as string]); + currentUser = userFoundInCache[0]; + if (currentUser === null) { + currentUser = await User.findOne({ _id: userId }).lean(); + if (currentUser !== null) { + await cacheUsers([currentUser]); + } + } + + if (!currentUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + // Get current user's app profile + let currentUserAppProfile: InterfaceAppUserProfile | null; + const appUserProfileFoundInCache = await findAppUserProfileCache([ + currentUser.appUserProfileId?.toString(), + ]); + currentUserAppProfile = appUserProfileFoundInCache[0]; + if (currentUserAppProfile === null) { + currentUserAppProfile = await AppUserProfile.findOne({ + userId: currentUser._id, + }).lean(); + if (currentUserAppProfile !== null) { + await cacheAppUserProfile([currentUserAppProfile]); + } + } + + if (!currentUserAppProfile) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Get the post + let post: InterfacePost | null; + const postFoundInCache = await findPostsInCache([postId]); + post = postFoundInCache[0]; + if (post === null) { + post = await Post.findOne({ _id: postId }).populate("file").lean(); + if (post !== null) { + await cachePosts([post]); + } + } + + if (!post) { + throw new errors.NotFoundError( + requestContext.translate(POST_NOT_FOUND_ERROR.MESSAGE), + POST_NOT_FOUND_ERROR.CODE, + POST_NOT_FOUND_ERROR.PARAM, + ); + } + + // Check if the user has the right to update the post + const currentUserIsPostCreator = post.creatorId.equals(userId); + const isSuperAdmin = currentUserAppProfile.isSuperAdmin; + const isAdminOfPostOrganization = currentUserAppProfile?.adminFor.some( + (orgID) => + orgID && + new Types.ObjectId(orgID?.toString()).equals(post?.organization), + ); + + if ( + !currentUserIsPostCreator && + !isAdminOfPostOrganization && + !isSuperAdmin + ) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Handle file upload and cleanup + let fileId: string | undefined; + const oldFileId = post.file?._id?.toString(); + const oldObjectKey = post.file.metadata?.objectKey; + + if (req.file) { + // Upload new file + const response = await uploadFile(req, res); + fileId = response._id?.toString(); + + // Clean up old file if it exists + if (oldFileId && oldObjectKey) { + await deleteFile(oldObjectKey, oldFileId); + } + } + + // Validate title and pinned status + if (title && !post.pinned) { + throw new errors.InputValidationError( + requestContext.translate(POST_NEEDS_TO_BE_PINNED.MESSAGE), + POST_NEEDS_TO_BE_PINNED.CODE, + ); + } else if (!title && post.pinned) { + throw new errors.InputValidationError( + requestContext.translate(PLEASE_PROVIDE_TITLE.MESSAGE), + PLEASE_PROVIDE_TITLE.CODE, + ); + } + + // Validate input lengths + if (title) { + const validationResultTitle = isValidString(title, 256); + if (!validationResultTitle.isLessThanMaxLength) { + throw new errors.InputValidationError( + requestContext.translate( + `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, + ), + LENGTH_VALIDATION_ERROR.CODE, + ); + } + } + + if (text) { + const validationResultText = isValidString(text, 500); + if (!validationResultText.isLessThanMaxLength) { + throw new errors.InputValidationError( + requestContext.translate( + `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in information`, + ), + LENGTH_VALIDATION_ERROR.CODE, + ); + } + } + + const updatedPost = await Post.findOneAndUpdate( + { _id: postId }, + { + ...(title && { title }), + ...(text && { text }), + ...(pinned !== undefined && { pinned }), + ...(fileId && { file: fileId }), + }, + { new: true }, + ).lean(); + + if (updatedPost !== null) { + await cachePosts([updatedPost]); + } + + res.status(200).json({ + post: updatedPost, + }); + } catch (error) { + console.error(error); + if (error instanceof Error) { + res.status(500).json({ error: error.message }); + } else { + res.status(500).json({ + error: requestContext.translate(INTERNAL_SERVER_ERROR.MESSAGE), + }); + } + } +}; diff --git a/src/REST/controllers/query/getFile.ts b/src/REST/controllers/query/getFile.ts new file mode 100644 index 00000000000..171c53ca489 --- /dev/null +++ b/src/REST/controllers/query/getFile.ts @@ -0,0 +1,40 @@ +import type { Request, Response } from "express"; +import { s3Client, BUCKET_NAME } from "../../../config/minio"; +import { GetObjectCommand } from "@aws-sdk/client-s3"; +import type { Readable } from "stream"; + +/** + * Middleware to retrieve a file from S3 storage. + * + * This function retrieves a file from an S3-compatible storage service using the provided key from the request parameters. + * If the file is found, it streams the file's content back to the client with the appropriate content type. + * If an error occurs during the retrieval, it logs the error and sends a 500 status code response. + * + * @param req - The Express request object, containing the key for the file in the parameters. + * @param res - The Express response object used to send the file back to the client. + * + * @returns A promise that resolves to void. The function either streams the file or sends an error response. + * + * @example + * ```typescript + * app.get("/file/:key*", getFile); + * ``` + */ +export const getFile = async (req: Request, res: Response): Promise => { + const key = req.params[0]; + const command = new GetObjectCommand({ + Bucket: BUCKET_NAME, + Key: key, + }); + + try { + const data = await s3Client.send(command); + const stream = data.Body as Readable; + res.setHeader("Content-Type", data.ContentType as string); + res.setHeader("Cross-Origin-Resource-Policy", "same-site"); + stream.pipe(res); + } catch (error) { + console.error("Error fetching file:", error); + res.status(500).send("Error occurred while fetching file"); + } +}; diff --git a/src/REST/controllers/query/index.ts b/src/REST/controllers/query/index.ts new file mode 100644 index 00000000000..1a94d7580d4 --- /dev/null +++ b/src/REST/controllers/query/index.ts @@ -0,0 +1 @@ +export * from "./getFile"; diff --git a/src/REST/routes/index.ts b/src/REST/routes/index.ts new file mode 100644 index 00000000000..54b74a44cfd --- /dev/null +++ b/src/REST/routes/index.ts @@ -0,0 +1,31 @@ +// routes/fileRoutes.ts +import express from "express"; + +import { getFile } from "../controllers/query/getFile"; +import { createPost, updatePost } from "../controllers/mutation"; + +import { isAuthMiddleware } from "../../middleware"; +import { fileUpload } from "../../middleware/fileUpload"; + +const router = express.Router(); + +// Routes + +// Routes +/** + * Retrieves a file by its key. + * isAuthMiddleware - Authenticates the user. + * getFile - Handles fetching the requested file. + */ +router.get("/file/*", getFile); + +router.post("/create-post", isAuthMiddleware, fileUpload("file"), createPost); + +router.post( + "/update-post/:id", + isAuthMiddleware, + fileUpload("file"), + updatePost, +); + +export default router; diff --git a/src/REST/services/file/createFile.ts b/src/REST/services/file/createFile.ts new file mode 100644 index 00000000000..21586598a9a --- /dev/null +++ b/src/REST/services/file/createFile.ts @@ -0,0 +1,55 @@ +import { BUCKET_NAME } from "../../../config/minio"; +import { BASE_URL } from "../../../constants"; +import type { InterfaceFile } from "../../../models"; +import { File } from "../../../models"; +import type { InterfaceUploadResult } from "../minio"; + +/** + * Creates or updates a file document in the database based on the upload result. + * + * This function checks if a file with the same hash already exists. If it does, the reference count of the file is incremented. + * If not, a new file document is created and saved to the database. + * + * @param uploadResult - The result from the file upload containing the hash, object key, and hash algorithm. + * @param originalname - The original name of the uploaded file. + * @param mimetype - The MIME type of the uploaded file. + * @param size - The size of the uploaded file in bytes. + * @returns A promise that resolves to the created or updated file document. + * + * @example + * ```typescript + * const file = await createFile(uploadResult, "image.png", "image/png", 2048); + * console.log(file); + * ``` + */ +export const createFile = async ( + uploadResult: InterfaceUploadResult, + originalname: string, + mimetype: string, + size: number, +): Promise => { + const existingFile = await File.findOne({ "hash.value": uploadResult.hash }); + + if (existingFile) { + existingFile.referenceCount += 1; + await existingFile.save(); + return existingFile; + } + + const newFileDoc = await File.create({ + fileName: originalname, + mimeType: mimetype, + size: size, + hash: { + value: uploadResult.hash, + algorithm: uploadResult.hashAlgorithm, + }, + uri: `${BASE_URL}api/file/${uploadResult.objectKey}`, + metadata: { + objectKey: uploadResult.objectKey, + bucketName: BUCKET_NAME, + }, + }); + + return newFileDoc; +}; diff --git a/src/REST/services/file/deleteFile.ts b/src/REST/services/file/deleteFile.ts new file mode 100644 index 00000000000..363da6fcd9d --- /dev/null +++ b/src/REST/services/file/deleteFile.ts @@ -0,0 +1,35 @@ +import { File } from "../../../models"; +import { deleteFile as deleteFileFromBucket } from "../minio"; +import { BUCKET_NAME } from "../../../config/minio"; + +export const deleteFile = async ( + objectKey: string, + fileId: string, +): Promise<{ success: boolean; message: string }> => { + try { + const file = await File.findOne({ + _id: fileId, + "metadata.objectKey": objectKey, + }); + + if (!file) { + return { success: false, message: "File not found." }; + } + + if (file.referenceCount > 1) { + file.referenceCount -= 1; + await file.save(); + return { + success: true, + message: "File reference count decreased successfully", + }; + } + + await File.deleteOne({ _id: file.id }); + await deleteFileFromBucket(BUCKET_NAME as string, objectKey); + return { success: true, message: "File deleted successfully" }; + } catch (error) { + console.error("Error deleting file:", error); + return { success: false, message: "Error occurred while deleting file" }; + } +}; diff --git a/src/REST/services/file/index.ts b/src/REST/services/file/index.ts new file mode 100644 index 00000000000..e614f3ec1e7 --- /dev/null +++ b/src/REST/services/file/index.ts @@ -0,0 +1,3 @@ +export { createFile } from "./createFile"; +export { deleteFile } from "./deleteFile"; +export { uploadFile } from "./uploadFile"; diff --git a/src/REST/services/file/uploadFile.ts b/src/REST/services/file/uploadFile.ts new file mode 100644 index 00000000000..36408b8b054 --- /dev/null +++ b/src/REST/services/file/uploadFile.ts @@ -0,0 +1,79 @@ +import type { Request, Response } from "express"; + +import { uploadMedia } from "../minio"; +import { createFile } from "./createFile"; +import { BUCKET_NAME } from "../../../config/minio"; + +import { isValidMimeType } from "../../../utilities/isValidMimeType"; + +import type { InterfaceFile } from "../../../models"; +import { errors, requestContext } from "../../../libraries"; +import { + FILE_NOT_FOUND, + INTERNAL_SERVER_ERROR, + INVALID_FILE_TYPE, +} from "../../../constants"; + +export interface InterfaceUploadedFileResponse extends Partial { + objectKey: string; +} + +/** + * Handles file upload. + * @param req - The HTTP request object containing the file. + * @param res - The HTTP response object used to send the response. + * @throws Error - Throws an error if no file is uploaded or if the file type is invalid. + * @returns UploadedFileResponse - The response containing file ID and object key. + */ +export const uploadFile = async ( + req: Request, + res: Response, +): Promise => { + if (!req.file) { + res + .status(400) + .json({ error: requestContext.translate(FILE_NOT_FOUND.MESSAGE) }); + throw new errors.InputValidationError( + requestContext.translate(FILE_NOT_FOUND.MESSAGE), + FILE_NOT_FOUND.CODE, + ); + } + + const { mimetype, originalname, buffer, size } = req.file; + + if (!isValidMimeType(mimetype)) { + throw new errors.InputValidationError( + requestContext.translate(INVALID_FILE_TYPE.MESSAGE), + INVALID_FILE_TYPE.CODE, + ); + } + + try { + const contentType = { ContentType: mimetype }; + const uploadedFile = await uploadMedia( + BUCKET_NAME as string, + buffer, + originalname, + contentType, + ); + const fileDoc = await createFile( + uploadedFile, + originalname, + mimetype, + size, + ); + + return { + uri: fileDoc.uri, + _id: fileDoc._id, + visibility: fileDoc.visibility, + objectKey: fileDoc.metadata.objectKey, + }; + } catch (error) { + console.error("Error", error); + throw new errors.InternalServerError( + requestContext.translate(INTERNAL_SERVER_ERROR.MESSAGE), + INTERNAL_SERVER_ERROR.CODE, + ); + } +}; diff --git a/src/REST/services/minio/index.ts b/src/REST/services/minio/index.ts new file mode 100644 index 00000000000..e9d37beb65b --- /dev/null +++ b/src/REST/services/minio/index.ts @@ -0,0 +1,130 @@ +// Import third-party modules +import crypto from "crypto"; +import path from "path"; + +// Import AWS SDK S3 client and commands +import { + DeleteObjectCommand, + HeadObjectCommand, + PutObjectCommand, +} from "@aws-sdk/client-s3"; +import type { DeleteObjectCommandOutput } from "@aws-sdk/client-s3"; + +// Import project configuration +import { s3Client } from "../../../config/minio"; + +export interface InterfaceUploadResult { + exists: boolean; + objectKey: string; + hash: string; + hashAlgorithm: string; +} + +/** + * Uploads a media file to a specified S3 bucket, calculating its hash for naming and uniqueness. + * + * The `uploadMedia` function calculates the SHA-256 hash of the provided buffer to generate a unique object key. + * It first checks if a file with the same hash already exists in the bucket using the `HeadObjectCommand`. + * If the file does not exist, it uploads the file using the `PutObjectCommand`. It supports both image and video uploads + * by assigning appropriate prefixes to the object key. + * + * @param bucketName - The name of the S3 bucket where the file will be uploaded. + * @param buffer - The file content as a buffer. + * @param originalname - The original file name, used to determine the file extension. + * @param contentType - An object specifying the content type of the file. + * @returns A promise that resolves to an object containing the file's existence status, object key, hash, and hash algorithm. + * + * @example + * ```typescript + * const result = await uploadMedia("my-bucket", fileBuffer, "image.png", { ContentType: "image/png" }); + * console.log(result); + * ``` + */ +export const uploadMedia = async ( + bucketName: string, + buffer: Buffer, + originalname: string, + contentType: { ContentType: string }, +): Promise => { + const hash = crypto.createHash("sha256").update(buffer).digest("hex"); + const fileExtension = path.extname(originalname); + + let prefix = ""; + if (contentType.ContentType.startsWith("image/")) { + prefix = "image/"; + } else if (contentType.ContentType.startsWith("video/")) { + prefix = "video/"; + } + + const objectKey = `${prefix}${hash}${fileExtension}`; + + const headParams = { + Bucket: bucketName, + Key: objectKey, + }; + const headCommand = new HeadObjectCommand(headParams); + + try { + await s3Client.send(headCommand); + return { exists: true, objectKey, hash, hashAlgorithm: "sha256" }; + } catch (error: unknown) { + if ( + error instanceof Error && + "name" in error && + error.name === "NotFound" + ) { + const params = { + Bucket: bucketName, + Key: objectKey, + Body: buffer, + ...contentType, + }; + + try { + const command = new PutObjectCommand(params); + await s3Client.send(command); + return { exists: false, objectKey, hash, hashAlgorithm: "sha256" }; + } catch (uploadError: unknown) { + console.error("Error uploading the file:", uploadError); + throw uploadError; + } + } else { + console.error("Error checking file existence:", error); + throw error; + } + } +}; + +/** + * Deletes a file from a specified S3 bucket. + * + * The `deleteFile` function deletes an object in an S3 bucket using the `DeleteObjectCommand`. + * If an error occurs during the deletion process, it logs the error and rethrows it. + * + * @param bucketName - The name of the S3 bucket from which the file will be deleted. + * @param objectKey - The key of the object to be deleted in the S3 bucket. + * @returns A promise that resolves to the output of the `DeleteObjectCommand`. + * + * @example + * ```typescript + * const response = await deleteFile("my-bucket", "image123.png"); + * console.log(response); + * ``` + */ +export const deleteFile = async ( + bucketName: string, + objectKey: string, +): Promise => { + const params = { + Bucket: bucketName, + Key: objectKey, + }; + const command = new DeleteObjectCommand(params); + try { + const response = await s3Client.send(command); + return response; + } catch (error) { + console.error("Error deleting file:", error); + throw error; + } +}; diff --git a/src/REST/types/index.ts b/src/REST/types/index.ts new file mode 100644 index 00000000000..4725dcca529 --- /dev/null +++ b/src/REST/types/index.ts @@ -0,0 +1,9 @@ +/** + * Allowed MIME types for files. + */ +export type FileMimeType = + | "image/jpeg" + | "image/png" + | "image/gif" + | "image/webp" + | "video/mp4"; diff --git a/src/app.ts b/src/app.ts index a5571597a49..e8e0f4eb867 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,19 +6,21 @@ import { express as voyagerMiddleware } from "graphql-voyager/middleware"; import helmet from "helmet"; import i18n from "i18n"; import requestLogger from "morgan"; -import path from "path"; import { appConfig } from "./config"; import { requestContext, requestTracing, stream } from "./libraries"; -import graphqlUploadExpress from "graphql-upload/graphqlUploadExpress.mjs"; +import routes from "./REST/routes"; + +import * as enLocale from "../locales/en.json"; +import * as hiLocale from "../locales/hi.json"; +import * as zhLocale from "../locales/zh.json"; +import * as spLocale from "../locales/sp.json"; +import * as frLocale from "../locales/fr.json"; const app = express(); // Middleware for tracing requests app.use(requestTracing.middleware()); -// Initialize i18n for internationalization -app.use(i18n.init); - // Rate limiting middleware to prevent abuse const apiLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour window @@ -27,7 +29,6 @@ const apiLimiter = rateLimit({ }); app.use(apiLimiter); -// eslint-disable-next-line @typescript-eslint/no-unused-vars const corsOptions: cors.CorsOptions = { origin: (origin, next) => { if (process.env.NODE_ENV === "development") { @@ -42,17 +43,18 @@ const corsOptions: cors.CorsOptions = { } next(new Error("Unauthorized")); // Reject other origins }, + optionsSuccessStatus: 200, }; // Configure i18n settings i18n.configure({ directory: `${__dirname}/../locales`, staticCatalog: { - en: require("../locales/en.json"), - hi: require("../locales/hi.json"), - zh: require("../locales/zh.json"), - sp: require("../locales/sp.json"), - fr: require("../locales/fr.json"), + en: enLocale, + hi: hiLocale, + zh: zhLocale, + sp: spLocale, + fr: frLocale, }, queryParameter: "lang", defaultLocale: appConfig.defaultLocale, @@ -73,20 +75,11 @@ app.use( // Sanitize data to prevent MongoDB operator injection app.use(mongoSanitize()); -app.use(cors()); - -// Serve static files with Cross-Origin-Resource-Policy header set -app.use("/images", (req, res, next) => { - res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); - next(); -}); +app.use(cors(corsOptions)); // Parse JSON requests with a size limit of 50mb app.use(express.json({ limit: "50mb" })); -// Handle file uploads using graphql-upload -app.use(graphqlUploadExpress()); - // Parse URL-encoded requests with a size limit of 50mb app.use(express.urlencoded({ limit: "50mb", extended: true })); @@ -100,13 +93,11 @@ app.use( ), ); -// Serve static files for images and videos -app.use("/images", express.static(path.join(__dirname, "./../images"))); -app.use("/videos", express.static(path.join(__dirname, "./../videos"))); - // Middleware for managing request context (e.g., user session) app.use(requestContext.middleware()); +app.use("/api", routes); + // Enable GraphQL Voyager visualization in development if (process.env.NODE_ENV !== "production") { app.use("/voyager", voyagerMiddleware({ endpointUrl: "/graphql" })); diff --git a/src/config/minio/index.ts b/src/config/minio/index.ts new file mode 100644 index 00000000000..64fc4911d74 --- /dev/null +++ b/src/config/minio/index.ts @@ -0,0 +1,47 @@ +import { S3Client } from "@aws-sdk/client-s3"; + +/** + * Initializes and exports an S3 client instance using AWS SDK for connecting to MinIO storage. + * + * The `s3Client` is an instance of the AWS S3 client configured to interact with a MinIO storage service. + * The client uses custom endpoint, credentials, and region details from environment variables to + * establish the connection. It also forces path-style access to ensure compatibility with MinIO. + * + * **Environment Variables:** + * - `MINIO_ENDPOINT`: The MinIO storage endpoint URL. + * - `MINIO_ROOT_USER`: The access key ID for the MinIO instance. + * - `MINIO_ROOT_PASSWORD`: The secret access key for the MinIO instance. + * - `MINIO_BUCKET`: The default bucket name in MinIO. + * + * @example + * ```typescript + * import { s3Client } from './path/to/file'; + * + * // Example usage + * const data = await s3Client.send(new ListBucketsCommand({})); + * console.log(data.Buckets); + * ``` + * + * @returns S3Client - an instance of the AWS S3 client configured for MinIO storage. + */ +export const s3Client = new S3Client({ + endpoint: process.env.MINIO_ENDPOINT, + credentials: { + accessKeyId: process.env.MINIO_ROOT_USER as string, + secretAccessKey: process.env.MINIO_ROOT_PASSWORD as string, + }, + region: process.env.MNIO_REGION, + forcePathStyle: true, +}); + +/** + * The name of the bucket used in the MinIO storage, defined via an environment variable. + * + * @example + * ```typescript + * console.log(BUCKET_NAME); // Logs the bucket name from the environment + * ``` + * + * @returns The name of the MinIO bucket. + */ +export const BUCKET_NAME = process.env.MINIO_BUCKET; diff --git a/src/config/multer/index.ts b/src/config/multer/index.ts new file mode 100644 index 00000000000..ce13a2a9d46 --- /dev/null +++ b/src/config/multer/index.ts @@ -0,0 +1,69 @@ +import multer from "multer"; +import { + VIDEO_SIZE_LIMIT, + ALLOWED_IMAGE_TYPES, + ALLOWED_VIDEO_TYPES, + INVALID_FILE_TYPE, +} from "../../constants"; +import type { Request } from "express"; +import { errors, requestContext } from "../../libraries"; + +/** + * File filter function for multer. + * + * This function checks the MIME type of the uploaded file against allowed image and video types. + * If the file type is valid, it calls the callback with `true`. Otherwise, it calls the callback + * with an error message. + * + * @param req - The Express request object. + * @param file - The file being uploaded. + * @param cb - The callback function to indicate if the file is accepted or rejected. + * + * @example + * ```typescript + * fileFilter(req, file, cb); + * ``` + */ +export const fileFilter = ( + req: Request, + file: Express.Multer.File, + cb: multer.FileFilterCallback, +): void => { + if (ALLOWED_IMAGE_TYPES.includes(file.mimetype)) { + cb(null, true); + } else if (ALLOWED_VIDEO_TYPES.includes(file.mimetype)) { + cb(null, true); + } else { + cb( + new errors.InvalidFileTypeError( + requestContext.translate(INVALID_FILE_TYPE.MESSAGE), + INVALID_FILE_TYPE.CODE, + INVALID_FILE_TYPE.PARAM, + ), + ); + } +}; + +/** + * Multer upload configuration. + * + * This configuration sets up multer to use memory storage, applies the file filter, + * and sets a file size limit for uploads. + * + * @returns A multer instance configured for handling uploads. + * + * @example + * ```typescript + * const uploadMiddleware = upload.single("file"); + * app.post("/upload", uploadMiddleware, (req, res) => { + * res.send("File uploaded successfully!"); + * }); + * ``` + */ +export const upload = multer({ + storage: multer.memoryStorage(), + fileFilter, + limits: { + fileSize: VIDEO_SIZE_LIMIT + 2, + }, +}); diff --git a/src/constants.ts b/src/constants.ts index fab3f2b12d2..b30914dd5d2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -43,6 +43,13 @@ export const AGENDA_CATEGORY_NOT_FOUND_ERROR = Object.freeze({ PARAM: "agendaCategory", }); +export const APP_USER_PROFILE_NOT_FOUND_ERROR = Object.freeze({ + DESC: "appUserProfile not found", + CODE: "appUserProfile.notFound", + MESSAGE: "appUserProfile.notFound", + PARAM: "appUserProfile", +}); + export const BASE_RECURRING_EVENT_NOT_FOUND = Object.freeze({ DESC: "Base Recurring Event not found", CODE: "baseRecurringEvent.notFound", @@ -57,6 +64,20 @@ export const CHAT_NOT_FOUND_ERROR = Object.freeze({ PARAM: "chat", }); +export const MESSAGE_NOT_FOUND_ERROR = Object.freeze({ + DESC: "Message not found", + CODE: "message.notFound", + MESSAGE: "message.notFound", + PARAM: "message", +}); + +export const COMMUNITY_NOT_FOUND_ERROR = Object.freeze({ + DESC: "Community not found", + CODE: "community.notFound", + MESSAGE: "community.notFound", + PARAM: "community", +}); + export const VENUE_ALREADY_EXISTS_ERROR = Object.freeze({ DESC: "Venue already exists", CODE: "venue.alreadyExists", @@ -144,6 +165,14 @@ export const FUND_NOT_FOUND_ERROR = Object.freeze({ export const INVALID_OTP = "Invalid OTP"; export const IN_PRODUCTION = process.env.NODE_ENV === "production"; + +export const INVALID_TIMEOUT_RANGE = Object.freeze({ + DESC: "Timeout should be in the range of 15 to 60 minutes.", + CODE: "invalid.timeoutRange", + MESSAGE: "invalid.timeoutRange", + PARAM: "timeout", +}); + export const MEMBER_NOT_FOUND_ERROR = Object.freeze({ DESC: "Member not found", CODE: "member.notFound", @@ -442,8 +471,7 @@ export const NO_CHANGE_IN_TAG_NAME = Object.freeze({ }); export const TAG_ALREADY_EXISTS = Object.freeze({ - MESSAGE: - "A tag with the same name and the same parent tag already exists for this organization.", + MESSAGE: "A tag with the same name already exists at this level", CODE: "tag.alreadyExists", PARAM: "tag.alreadyExists", }); @@ -640,6 +668,13 @@ export const EVENT_VOLUNTEER_INVITE_USER_MISTMATCH = Object.freeze({ PARAM: "eventVolunteers", }); +export const EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR = Object.freeze({ + DESC: "Volunteer membership not found", + CODE: "volunteerMembership.notFound", + MESSAGE: "volunteerMembership.notFound", + PARAM: "volunteerMemberships", +}); + export const USER_ALREADY_CHECKED_IN = Object.freeze({ MESSAGE: "The user has already been checked in for this event.", CODE: "user.alreadyCheckedIn", @@ -696,6 +731,39 @@ export const PRELOGIN_IMAGERY_FIELD_EMPTY = Object.freeze({ PARAM: "preLoginImagery.empty", }); +export const CONTENT_TYPE_SHOULD_BE_MULTIPART_FORM_DATA = Object.freeze({ + DESC: "Invalid content type. Expected multipart/form-data", + MESSAGE: "invalid.contentType", + CODE: "invalid.contentType", +}); + +export const INVALID_FILE_FIELD_NAME = Object.freeze({ + DESC: "Invalid file input field name received.", + MESSAGE: "invalid.fieldFileName", + CODE: "invalid.fieldFileName", +}); + +export const FILE_SIZE_EXCEEDED = Object.freeze({ + DESC: "File size exceeds the allowable limit", + MESSAGE: "file.sizeExceeded", + CODE: "file.sizeExceeded", +}); + +export const FILE_NOT_FOUND = Object.freeze({ + DESC: "File not found.", + MESSAGE: "file.notFound", + CODE: "file.notFound", +}); + +export const INVALID_ARGUMENT_RECEIVED = Object.freeze({ + DESC: "Invalid argument received.", + MESSAGE: "invalid.argument", + CODE: "invalid.argument", +}); + +export const MINIMUM_TIMEOUT_MINUTES = 15; +export const MAXIMUM_TIMEOUT_MINUTES = 60; + export const MAXIMUM_FETCH_LIMIT = 100; export const MAXIMUM_IMAGE_SIZE_LIMIT_KB = 20000; @@ -791,3 +859,14 @@ export const DEFAULT_COMMUNITY = { name: "Palisadoes Foundation", description: "An open source application by Palisadoes Foundation volunteers", }; + +export const ALLOWED_IMAGE_TYPES = [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", +]; +export const ALLOWED_VIDEO_TYPES = ["video/mp4", "video/mpeg"]; + +export const VIDEO_SIZE_LIMIT = 50 * 1024 * 1024; +export const IMAGE_SIZE_LIMIT = 5 * 1024 * 1024; diff --git a/src/env.ts b/src/env.ts index d49f7d3ce49..c409cb21270 100644 --- a/src/env.ts +++ b/src/env.ts @@ -32,6 +32,15 @@ export const envSchema = z.object({ REDIS_HOST: z.string(), REDIS_PORT: z.string().refine((value) => /^\d+$/.test(value)), REDIS_PASSWORD: z.string().optional(), + MINIO_ROOT_USER: z.string(), + MINIO_ROOT_PASSWORD: z.string(), + MINIO_BUCKET: z.string(), + MINIO_ENDPOINT: z + .string() + .url() + .refine((value: string) => + ["http://localhost:9000", "http://minio:9000"].includes(value), + ), }); export const getEnvIssues = (): z.ZodIssue[] | void => { diff --git a/src/middleware/fileUpload.ts b/src/middleware/fileUpload.ts new file mode 100644 index 00000000000..085d73d35f2 --- /dev/null +++ b/src/middleware/fileUpload.ts @@ -0,0 +1,74 @@ +import type { Request, Response, NextFunction, RequestHandler } from "express"; +import multer from "multer"; + +import { upload } from "../config/multer"; +import { + ALLOWED_IMAGE_TYPES, + CONTENT_TYPE_SHOULD_BE_MULTIPART_FORM_DATA, + FILE_SIZE_EXCEEDED, + IMAGE_SIZE_LIMIT, + INVALID_FILE_FIELD_NAME, + VIDEO_SIZE_LIMIT, +} from "../constants"; +import { requestContext } from "../libraries"; + +/** + * A middleware for handling optional file uploads. + * All data must be sent as multipart/form-data, but the file field is optional. + * + * @param fieldName - The name of the file field in the form + * @returns Express middleware for handling file upload + */ +export const fileUpload = (fieldName: string): RequestHandler => { + return (req: Request, res: Response, next: NextFunction): void => { + // Validate content type is multipart/form-data + const contentType = req.get("content-type"); + if (contentType && !contentType.includes("multipart/form-data")) { + res.status(400).json({ + error: requestContext.translate( + CONTENT_TYPE_SHOULD_BE_MULTIPART_FORM_DATA.MESSAGE, + ), + }); + return; + } + + // Handle file upload + upload.single(fieldName)(req, res, (err) => { + if (err instanceof multer.MulterError) { + if (err.code === "LIMIT_UNEXPECTED_FILE") { + res.status(400).json({ + error: requestContext.translate(INVALID_FILE_FIELD_NAME.MESSAGE), + }); + return; + } + res.status(400).json({ error: err.message }); + return; + } else if (err) { + res.status(500).json({ error: "File upload failed" }); + return; + } + + // If no file uploaded, continue + if (!req.file) { + next(); + return; + } + + // Validate file size if file was uploaded + const isImage = ALLOWED_IMAGE_TYPES.includes(req.file.mimetype); + const sizeLimit = isImage ? IMAGE_SIZE_LIMIT : VIDEO_SIZE_LIMIT; + + if (req.file.size > sizeLimit) { + const typeStr = isImage ? "Image" : "Video"; + const sizeMB = sizeLimit / (1024 * 1024); + res.status(400).json({ + error: requestContext.translate(FILE_SIZE_EXCEEDED.MESSAGE), + description: `${typeStr} size exceeds the limit of ${sizeMB}MB`, + }); + return; + } + + next(); + }); + }; +}; diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 065f1a55bcd..e4518e8e421 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,2 +1,3 @@ // Export everything from this module, including isAuth function export * from "./isAuth"; +export * from "./fileUpload"; diff --git a/src/middleware/isAuth.ts b/src/middleware/isAuth.ts index 7e96dcdf392..504abf1cdad 100644 --- a/src/middleware/isAuth.ts +++ b/src/middleware/isAuth.ts @@ -1,7 +1,7 @@ -import type { Request } from "express"; +import type { NextFunction, Request, Response } from "express"; import jwt from "jsonwebtoken"; -import { ACCESS_TOKEN_SECRET } from "../constants"; -import { logger } from "../libraries"; +import { ACCESS_TOKEN_SECRET, UNAUTHENTICATED_ERROR } from "../constants"; +import { logger, requestContext } from "../libraries"; // This interface represents the type of data object returned by isAuth function. export interface InterfaceAuthData { @@ -58,7 +58,8 @@ export const isAuth = (request: Request): InterfaceAuthData => { authData.expired = true; return authData; } - } catch (e) { + } catch (err) { + logger.error(err); authData.expired = true; return authData; } @@ -76,3 +77,53 @@ export const isAuth = (request: Request): InterfaceAuthData => { // Return the finalized authData object return authData; }; + +// Extend the Express Request interface locally +export interface InterfaceAuthenticatedRequest extends Request { + isAuth?: boolean; + userId?: string; + tokenExpired?: boolean; +} + +/** + * Middleware for REST APIs to authenticate users based on the JWT token in the Authorization header. + * + * This middleware checks if the incoming request has a valid JWT token. It sets the authentication + * status, user ID, and token expiration status on the `req` object for downstream middleware and + * route handlers to use. + * + * @param req - The incoming request object. The JWT token is expected in the `Authorization` header. + * @param res - The response object. If authentication fails, an HTTP 401 response will be sent. + * @param next - The next middleware function in the stack. It is called if the user is authenticated. + * + * @returns Returns a 401 Unauthorized response if the user is not authenticated or the token has expired. + * + * @example + * ```typescript + * app.use("/api/protected-route", isAuthMiddleware, (req, res) => { + * if (req.isAuth) { + * res.json({ message: "This is a protected route" }); + * } + * }); + * ``` + */ +export const isAuthMiddleware = ( + req: InterfaceAuthenticatedRequest, + res: Response, + next: NextFunction, +): void => { + const authData: InterfaceAuthData = isAuth(req); + req.isAuth = authData.isAuth; + req.userId = authData.userId; + req.tokenExpired = authData.expired; + + if (!authData.isAuth) { + res.status(401).json({ + message: requestContext.translate(UNAUTHENTICATED_ERROR.MESSAGE), + expired: authData.expired, + }); + return; + } + + next(); +}; diff --git a/src/minioInstallationCheck.ts b/src/minioInstallationCheck.ts new file mode 100644 index 00000000000..628e480df9d --- /dev/null +++ b/src/minioInstallationCheck.ts @@ -0,0 +1,79 @@ +import * as os from "os"; +import * as path from "path"; +import { spawnSync } from "child_process"; +import * as dotenv from "dotenv"; +import { isMinioInstalled } from "./setup/isMinioInstalled"; +import { installMinio } from "./setup/installMinio"; + +dotenv.config(); + +/** + * Checks if MinIO is installed by attempting to execute `minio --version`. + * If MinIO is not installed, it triggers the installation process. + * + * This function first checks if MinIO is already installed by calling `isMinioInstalled()`. + * - If MinIO is found to be installed, it logs a message and resolves with no value. + * - If MinIO is not found, it initiates the installation process using `installMinio()`. + * - If the installation succeeds, it logs a success message and resolves with the path to the installed MinIO binary. + * - If the installation fails, it logs an error message and rejects the promise with the error. + * + * @returns A promise that resolves with: + * - The path to the MinIO binary if it was installed. + * - No value if MinIO was already installed. + * @throws Error If an error occurs during the check or installation process. + */ +export const checkMinio = async (): Promise => { + try { + if (isMinioInstalled()) { + console.log("[MINIO] Minio is already installed."); + return; + } else { + console.log("[MINIO] Minio is not installed."); + console.log("[MINIO] Installing Minio..."); + try { + const minioPath = await installMinio(); + console.log("[MINIO] Minio installed successfully.\n"); + return minioPath; + } catch (err) { + console.error("[MINIO] Failed to install Minio:", err); + throw err; + } + } + } catch (err) { + console.error("[MINIO] An error occurred:", err); + throw err; + } +}; + +// Start MinIO installation or verification process +checkMinio() + .then((minioPath) => { + console.log("[MINIO] Starting server..."); + console.info( + "\x1b[1m\x1b[32m%s\x1b[0m", + "[MINIO] Minio started successfully!", + ); + const minioCommand = + minioPath || + path.join( + os.homedir(), + ".minio", + `minio${os.platform() === "win32" ? ".exe" : ""}`, + ); + const dataDir = process.env.MINIO_DATA_DIR || "./data"; + spawnSync(minioCommand, ["server", dataDir, "--console-address", ":9001"], { + env: { + ...process.env, + MINIO_ROOT_USER: process.env.MINIO_ROOT_USER, + MINIO_ROOT_PASSWORD: process.env.MINIO_ROOT_PASSWORD, + }, + stdio: "inherit", + }); + }) + .catch((err) => { + console.error( + "\x1b[1m\x1b[31m%s\x1b[0m", + "[MINIO] Failed to install or start Minio:", + err, + ); + }); diff --git a/src/models/ActionItem.ts b/src/models/ActionItem.ts index fe99964a0af..94904a25a75 100644 --- a/src/models/ActionItem.ts +++ b/src/models/ActionItem.ts @@ -5,13 +5,18 @@ import type { InterfaceEvent } from "./Event"; import type { InterfaceActionItemCategory } from "./ActionItemCategory"; import { MILLISECONDS_IN_A_WEEK } from "../constants"; import type { InterfaceOrganization } from "./Organization"; +import type { InterfaceEventVolunteerGroup } from "./EventVolunteerGroup"; +import type { InterfaceEventVolunteer } from "./EventVolunteer"; /** * Interface representing a database document for ActionItem in MongoDB. */ export interface InterfaceActionItem { _id: Types.ObjectId; - assignee: PopulatedDoc; + assignee: PopulatedDoc; + assigneeGroup: PopulatedDoc; + assigneeUser: PopulatedDoc; + assigneeType: "EventVolunteer" | "EventVolunteerGroup" | "User"; assigner: PopulatedDoc; actionItemCategory: PopulatedDoc< InterfaceActionItemCategory & Document @@ -22,7 +27,7 @@ export interface InterfaceActionItem { dueDate: Date; completionDate: Date; isCompleted: boolean; - allotedHours: number | null; + allottedHours: number | null; organization: PopulatedDoc; event: PopulatedDoc; creator: PopulatedDoc; @@ -33,6 +38,9 @@ export interface InterfaceActionItem { /** * Defines the schema for the ActionItem document. * @param assignee - User to whom the ActionItem is assigned. + * @param assigneeGroup - Group to whom the ActionItem is assigned. + * @param assigneeUser - Organization User to whom the ActionItem is assigned. + * @param assigneeType - Type of assignee (User or Group). * @param assigner - User who assigned the ActionItem. * @param actionItemCategory - ActionItemCategory to which the ActionItem belongs. * @param preCompletionNotes - Notes recorded before completion. @@ -41,7 +49,7 @@ export interface InterfaceActionItem { * @param dueDate - Due date for the ActionItem. * @param completionDate - Date when the ActionItem was completed. * @param isCompleted - Flag indicating if the ActionItem is completed. - * @param allotedHours - Optional: Number of hours alloted for the ActionItem. + * @param allottedHours - Optional: Number of hours allotted for the ActionItem. * @param event - Optional: Event to which the ActionItem is related. * @param organization - Organization to which the ActionItem belongs. * @param creator - User who created the ActionItem. @@ -51,9 +59,21 @@ export interface InterfaceActionItem { const actionItemSchema = new Schema( { assignee: { + type: Schema.Types.ObjectId, + ref: "EventVolunteer", + }, + assigneeGroup: { + type: Schema.Types.ObjectId, + ref: "EventVolunteerGroup", + }, + assigneeUser: { type: Schema.Types.ObjectId, ref: "User", + }, + assigneeType: { + type: String, required: true, + enum: ["EventVolunteer", "EventVolunteerGroup", "User"], }, assigner: { type: Schema.Types.ObjectId, @@ -91,7 +111,7 @@ const actionItemSchema = new Schema( required: true, default: false, }, - allotedHours: { + allottedHours: { type: Number, }, organization: { diff --git a/src/models/DirectChat.ts b/src/models/Chat.ts similarity index 51% rename from src/models/DirectChat.ts rename to src/models/Chat.ts index 3715e0f6878..563550d7bb2 100644 --- a/src/models/DirectChat.ts +++ b/src/models/Chat.ts @@ -1,34 +1,42 @@ import type { PopulatedDoc, Types, Document, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; -import type { InterfaceDirectChatMessage } from "./DirectChatMessage"; import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; import { createLoggingMiddleware } from "../libraries/dbLogger"; +import type { InterfaceChatMessage } from "./ChatMessage"; /** * Interface representing a document for direct chat in MongoDB. */ -export interface InterfaceDirectChat { +export interface InterfaceChat { _id: Types.ObjectId; + isGroup: boolean; + name: string; users: PopulatedDoc[]; - messages: PopulatedDoc[]; + messages: PopulatedDoc[]; creatorId: PopulatedDoc; organization: PopulatedDoc; status: string; + admins: PopulatedDoc[]; createdAt: Date; updatedAt: Date; + lastMessageId: string; } /** - * Mongoose schema for a direct chat. + * Mongoose schema for a chat. + * @param isGroup - Indicates if the chat is a group chat. + * @param name - Name of the chat if its a group chat. * @param users - Users participating in the chat. * @param messages - Messages in the chat. * @param creatorId - Creator of the chat, reference to `User` model. + * @param admins - Admins of the chat if its a group chat, reference to `User` model. * @param organization - Organization associated with the chat, reference to `Organization` model. * @param status - Status of the chat (ACTIVE, BLOCKED, DELETED). * @param createdAt - Timestamp of chat creation. * @param updatedAt - Timestamp of chat update. + * @param lastMessageId - ID of the last message in the chat. */ -const directChatSchema = new Schema( +const chatSchema = new Schema( { users: [ { @@ -37,10 +45,22 @@ const directChatSchema = new Schema( required: true, }, ], + isGroup: { + type: Boolean, + required: true, + }, + name: { + type: String, + required: false, + }, + image: { + type: String, + require: false, + }, messages: [ { type: Schema.Types.ObjectId, - ref: "DirectChatMessage", + ref: "ChatMessage", }, ], creatorId: { @@ -48,28 +68,53 @@ const directChatSchema = new Schema( ref: "User", required: true, }, + admins: [ + { + type: Schema.Types.ObjectId, + ref: "User", + required: false, + }, + ], status: { type: String, required: true, enum: ["ACTIVE", "BLOCKED", "DELETED"], default: "ACTIVE", }, + organization: { + type: Schema.Types.ObjectId, + ref: "Organization", + required: false, + }, + createdAt: { + type: Date, + required: true, + }, + updatedAt: { + type: Date, + required: true, + }, + lastMessageId: { + type: String, + required: false, + }, }, { timestamps: true, }, ); -// Add logging middleware for directChatSchema -createLoggingMiddleware(directChatSchema, "DirectChat"); +// Add logging middleware for Chat +createLoggingMiddleware(chatSchema, "Chat"); /** - * Retrieves or creates the Mongoose model for DirectChat. + * Retrieves or creates the Mongoose model for Chat. * Prevents Mongoose OverwriteModelError during testing. */ -const directChatModel = (): Model => - model("DirectChat", directChatSchema); +const chatModel = (): Model => + model("Chat", chatSchema); // This syntax is needed to prevent Mongoose OverwriteModelError while running tests. -export const DirectChat = (models.DirectChat || - directChatModel()) as ReturnType; +export const Chat = (models.Chat || chatModel()) as ReturnType< + typeof chatModel +>; diff --git a/src/models/ChatMessage.ts b/src/models/ChatMessage.ts new file mode 100644 index 00000000000..1b2d9529a3f --- /dev/null +++ b/src/models/ChatMessage.ts @@ -0,0 +1,109 @@ +import type { PopulatedDoc, Types, Document, Model } from "mongoose"; +import { Schema, model, models } from "mongoose"; +import type { InterfaceUser } from "./User"; +import { createLoggingMiddleware } from "../libraries/dbLogger"; +import type { InterfaceChat } from "./Chat"; + +/** + * Represents a document for a chat message in the MongoDB database. + */ +export interface InterfaceChatMessage { + _id: Types.ObjectId; + chatMessageBelongsTo: PopulatedDoc; + sender: PopulatedDoc; + replyTo: PopulatedDoc; + messageContent: string; + status: string; + deletedBy: PopulatedDoc[]; + createdAt: Date; + updatedAt: Date; +} + +/** + * ChatMessage Schema + * + * This schema defines the structure of a chat message document in the database. + * + * Fields: + * - chatMessageBelongsTo: ObjectId, ref: "Chat", required + * - The chat to which this message belongs. + * - sender: ObjectId, ref: "User", required + * - The user who sent the message. + * - replyTo: ObjectId, ref: "ChatMessage", optional + * - The message to which this message is a reply. + * - messageContent: String, required + * - The content of the message. + * - type: String, required, enum: ["STRING", "VIDEO", "IMAGE", "FILE"] + * - The type of the message content. + * - status: String, required, enum: ["ACTIVE", "BLOCKED", "DELETED"], default: "ACTIVE" + * - The status of the message. + * - deletedBy: Array of ObjectId, ref: "User", optional + * - List of users who have deleted the message. + * - updatedAt: Date, required + * - The date when the message was last updated. + * - createdAt: Date, required + * - The date when the message was created. + * + * Options: + * - timestamps: Automatically adds createdAt and updatedAt fields. + */ +const chatMessageSchema = new Schema( + { + chatMessageBelongsTo: { + type: Schema.Types.ObjectId, + ref: "Chat", + required: true, + }, + sender: { + type: Schema.Types.ObjectId, + ref: "User", + required: true, + }, + replyTo: { + type: Schema.Types.ObjectId, + ref: "ChatMessage", + required: false, + }, + messageContent: { + type: String, + required: true, + }, + status: { + type: String, + required: true, + enum: ["ACTIVE", "BLOCKED", "DELETED"], + default: "ACTIVE", + }, + deletedBy: [ + { + type: Schema.Types.ObjectId, + ref: "User", + required: false, + }, + ], + updatedAt: { + type: Date, + required: true, + }, + createdAt: { + type: Date, + required: true, + }, + }, + { + timestamps: true, // Adds createdAt and updatedAt automatically + }, +); + +// Apply logging middleware to the schema +createLoggingMiddleware(chatMessageSchema, "ChatMessage"); + +/** + * Returns the Mongoose Model for ChatMessage to prevent OverwriteModelError. + */ +const chatMessageModel = (): Model => + model("ChatMessage", chatMessageSchema); + +// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +export const ChatMessage = (models.ChatMessage || + chatMessageModel()) as ReturnType; diff --git a/src/models/Community.ts b/src/models/Community.ts index 329254aae72..2301ed2e2aa 100644 --- a/src/models/Community.ts +++ b/src/models/Community.ts @@ -19,6 +19,7 @@ export interface InterfaceCommunity { slack: string; reddit: string; }; // Object containing various social media URLs for the community. + timeout: number; } /** @@ -35,6 +36,9 @@ export interface InterfaceCommunity { * @param youTube - YouTube URL. * @param slack - Slack URL. * @param reddit - Reddit URL. + * @param websiteLink - Community website URL. + * @param name - Community name. + * @param timeout - Timeout duration in minutes (default is 30 minutes). */ const communitySchema = new Schema({ name: { @@ -73,6 +77,12 @@ const communitySchema = new Schema({ type: String, }, }, + timeout: { + type: Number, + default: 30, + min: [15, "Timeout should be at least 15 minutes."], + max: [60, "Timeout should not exceed 60 minutes."], + }, }); /** diff --git a/src/models/DirectChatMessage.ts b/src/models/DirectChatMessage.ts deleted file mode 100644 index a31dcf809b7..00000000000 --- a/src/models/DirectChatMessage.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { PopulatedDoc, Types, Document, Model } from "mongoose"; -import { Schema, model, models } from "mongoose"; -import type { InterfaceDirectChat } from "./DirectChat"; -import type { InterfaceUser } from "./User"; -import { createLoggingMiddleware } from "../libraries/dbLogger"; - -/** - * Represents a document for a direct chat message in the MongoDB database. - */ -export interface InterfaceDirectChatMessage { - _id: Types.ObjectId; - directChatMessageBelongsTo: PopulatedDoc; - sender: PopulatedDoc; - receiver: PopulatedDoc; - messageContent: string; - status: string; - createdAt: Date; - updatedAt: Date; -} - -/** - * Mongoose schema definition for a direct chat message document. - * @param directChatMessageBelongsTo - Reference to the direct chat session to which the message belongs. - * @param sender - Reference to the user who sent the message. - * @param receiver - Reference to the user who received the message. - * @param messageContent - Content of the direct chat message. - * @param status - Status of the message (ACTIVE, BLOCKED, DELETED). - * @param createdAt - Date when the direct chat message was created. - * @param updatedAt - Date when the direct chat message was last updated. - */ -const directChatMessageSchema = new Schema( - { - directChatMessageBelongsTo: { - type: Schema.Types.ObjectId, - ref: "DirectChat", - required: true, - }, - sender: { - type: Schema.Types.ObjectId, - ref: "User", - required: true, - }, - receiver: { - type: Schema.Types.ObjectId, - ref: "User", - required: true, - }, - messageContent: { - type: String, - required: true, - }, - status: { - type: String, - required: true, - enum: ["ACTIVE", "BLOCKED", "DELETED"], - default: "ACTIVE", - }, - }, - { - timestamps: true, // Adds createdAt and updatedAt automatically - }, -); - -// Apply logging middleware to the schema -createLoggingMiddleware(directChatMessageSchema, "DirectChatMessage"); - -/** - * Returns the Mongoose Model for DirectChatMessage to prevent OverwriteModelError. - */ -const directChatMessageModel = (): Model => - model( - "DirectChatMessage", - directChatMessageSchema, - ); - -// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. -export const DirectChatMessage = (models.DirectChatMessage || - directChatMessageModel()) as ReturnType; diff --git a/src/models/Event.ts b/src/models/Event.ts index 2a30077aad6..f8536822716 100644 --- a/src/models/Event.ts +++ b/src/models/Event.ts @@ -6,6 +6,7 @@ import { createLoggingMiddleware } from "../libraries/dbLogger"; import type { InterfaceEventVolunteerGroup } from "./EventVolunteerGroup"; import type { InterfaceRecurrenceRule } from "./RecurrenceRule"; import type { InterfaceAgendaItem } from "./AgendaItem"; +import type { InterfaceEventVolunteer } from "./EventVolunteer"; /** * Represents a document for an event in the MongoDB database. @@ -37,6 +38,7 @@ export interface InterfaceEvent { startTime: string | undefined; title: string; updatedAt: Date; + volunteers: PopulatedDoc[]; volunteerGroups: PopulatedDoc[]; agendaItems: PopulatedDoc[]; } @@ -66,6 +68,7 @@ export interface InterfaceEvent { * @param admins - Array of admins for the event. * @param organization - Reference to the organization hosting the event. * @param volunteerGroups - Array of volunteer groups associated with the event. + * @param volunteers - Array of volunteers associated with the event. * @param createdAt - Timestamp of when the event was created. * @param updatedAt - Timestamp of when the event was last updated. */ @@ -178,6 +181,14 @@ const eventSchema = new Schema( ref: "Organization", required: true, }, + volunteers: [ + { + type: Schema.Types.ObjectId, + ref: "EventVolunteer", + required: true, + default: [], + }, + ], volunteerGroups: [ { type: Schema.Types.ObjectId, diff --git a/src/models/EventVolunteer.ts b/src/models/EventVolunteer.ts index 0600f77b4c7..72ccc24d621 100644 --- a/src/models/EventVolunteer.ts +++ b/src/models/EventVolunteer.ts @@ -4,6 +4,7 @@ import type { InterfaceUser } from "./User"; import type { InterfaceEvent } from "./Event"; import { createLoggingMiddleware } from "../libraries/dbLogger"; import type { InterfaceEventVolunteerGroup } from "./EventVolunteerGroup"; +import type { InterfaceActionItem } from "./ActionItem"; /** * Represents a document for an event volunteer in the MongoDB database. @@ -11,60 +12,95 @@ import type { InterfaceEventVolunteerGroup } from "./EventVolunteerGroup"; */ export interface InterfaceEventVolunteer { _id: Types.ObjectId; + creator: PopulatedDoc; + event: PopulatedDoc; + groups: PopulatedDoc[]; + user: PopulatedDoc; + hasAccepted: boolean; + isPublic: boolean; + hoursVolunteered: number; + assignments: PopulatedDoc[]; + hoursHistory: { + hours: number; + date: Date; + }[]; createdAt: Date; - creatorId: PopulatedDoc; - eventId: PopulatedDoc; - groupId: PopulatedDoc; - isAssigned: boolean; - isInvited: boolean; - response: string; updatedAt: Date; - userId: PopulatedDoc; } /** * Mongoose schema definition for an event volunteer document. * This schema defines how the data will be stored in the MongoDB database. * - * @param creatorId - Reference to the user who created the event volunteer entry. - * @param eventId - Reference to the event for which the user volunteers. - * @param groupId - Reference to the volunteer group associated with the event. - * @param response - Response status of the volunteer ("YES", "NO", null). - * @param isAssigned - Indicates if the volunteer is assigned to a specific role. - * @param isInvited - Indicates if the volunteer has been invited to participate. - * @param userId - Reference to the user who is volunteering for the event. + * @param creator - Reference to the user who created the event volunteer entry. + * @param event - Reference to the event for which the user volunteers. + * @param groups - Reference to the volunteer groups associated with the event. + * @param user - Reference to the user who is volunteering for the event. + * @param hasAccepted - Indicates if the volunteer has accepted invite. + * @param isPublic - Indicates if the volunteer is public. + * @param hoursVolunteered - Total hours volunteered by the user. + * @param assignments - List of action items assigned to the volunteer. * @param createdAt - Timestamp of when the event volunteer document was created. * @param updatedAt - Timestamp of when the event volunteer document was last updated. */ const eventVolunteerSchema = new Schema( { - creatorId: { + creator: { type: Schema.Types.ObjectId, ref: "User", required: true, }, - eventId: { + event: { type: Schema.Types.ObjectId, ref: "Event", }, - groupId: { + groups: [ + { + type: Schema.Types.ObjectId, + ref: "EventVolunteerGroup", + default: [], + }, + ], + user: { type: Schema.Types.ObjectId, - ref: "EventVolunteerGroup", - }, - response: { - type: String, - enum: ["YES", "NO", null], + ref: "User", + required: true, }, - isAssigned: { + hasAccepted: { type: Boolean, + required: true, + default: false, }, - isInvited: { + isPublic: { type: Boolean, - }, - userId: { - type: Schema.Types.ObjectId, - ref: "User", required: true, + default: true, + }, + hoursVolunteered: { + type: Number, + default: 0, + }, + assignments: [ + { + type: Schema.Types.ObjectId, + ref: "ActionItem", + default: [], + }, + ], + hoursHistory: { + type: [ + { + hours: { + type: Number, + required: true, + }, + date: { + type: Date, + required: true, + }, + }, + ], + default: [], }, }, { @@ -72,6 +108,9 @@ const eventVolunteerSchema = new Schema( }, ); +// Add index on hourHistory.date +eventVolunteerSchema.index({ "hourHistory.date": 1 }); + // Apply logging middleware to the schema createLoggingMiddleware(eventVolunteerSchema, "EventVolunteer"); diff --git a/src/models/EventVolunteerGroup.ts b/src/models/EventVolunteerGroup.ts index 8f8fc5072e4..4bd9f5f4386 100644 --- a/src/models/EventVolunteerGroup.ts +++ b/src/models/EventVolunteerGroup.ts @@ -4,6 +4,7 @@ import type { InterfaceUser } from "./User"; import type { InterfaceEvent } from "./Event"; import { createLoggingMiddleware } from "../libraries/dbLogger"; import type { InterfaceEventVolunteer } from "./EventVolunteer"; +import type { InterfaceActionItem } from "./ActionItem"; /** * Represents a document for an event volunteer group in the MongoDB database. @@ -11,42 +12,46 @@ import type { InterfaceEventVolunteer } from "./EventVolunteer"; */ export interface InterfaceEventVolunteerGroup { _id: Types.ObjectId; - createdAt: Date; - creatorId: PopulatedDoc; - eventId: PopulatedDoc; - leaderId: PopulatedDoc; + creator: PopulatedDoc; + event: PopulatedDoc; + leader: PopulatedDoc; name: string; - updatedAt: Date; + description?: string; volunteers: PopulatedDoc[]; volunteersRequired?: number; + assignments: PopulatedDoc[]; + createdAt: Date; + updatedAt: Date; } /** * Mongoose schema definition for an event volunteer group document. * This schema defines how the data will be stored in the MongoDB database. * - * @param creatorId - Reference to the user who created the event volunteer group entry. - * @param eventId - Reference to the event for which the volunteer group is created. - * @param leaderId - Reference to the leader of the volunteer group. + * @param creator - Reference to the user who created the event volunteer group entry. + * @param event - Reference to the event for which the volunteer group is created. + * @param leader - Reference to the leader of the volunteer group. * @param name - Name of the volunteer group. + * @param description - Description of the volunteer group (optional). * @param volunteers - List of volunteers in the group. * @param volunteersRequired - Number of volunteers required for the group (optional). + * @param assignments - List of action items assigned to the volunteer group. * @param createdAt - Timestamp of when the event volunteer group document was created. * @param updatedAt - Timestamp of when the event volunteer group document was last updated. */ const eventVolunteerGroupSchema = new Schema( { - creatorId: { + creator: { type: Schema.Types.ObjectId, ref: "User", required: true, }, - eventId: { + event: { type: Schema.Types.ObjectId, ref: "Event", required: true, }, - leaderId: { + leader: { type: Schema.Types.ObjectId, ref: "User", required: true, @@ -55,6 +60,9 @@ const eventVolunteerGroupSchema = new Schema( type: String, required: true, }, + description: { + type: String, + }, volunteers: [ { type: Schema.Types.ObjectId, @@ -65,6 +73,13 @@ const eventVolunteerGroupSchema = new Schema( volunteersRequired: { type: Number, }, + assignments: [ + { + type: Schema.Types.ObjectId, + ref: "ActionItem", + default: [], + }, + ], }, { timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields diff --git a/src/models/File.ts b/src/models/File.ts index bd8550f2dc1..a7336ce08d5 100644 --- a/src/models/File.ts +++ b/src/models/File.ts @@ -1,52 +1,119 @@ -import type { Types, Model } from "mongoose"; -import { Schema, model, models } from "mongoose"; +// Import third-party modules import { v4 as uuidv4 } from "uuid"; -import { createLoggingMiddleware } from "../libraries/dbLogger"; -/** - * This is an interface representing a document for a file in the database(MongoDB). - */ +import { Schema, model, models } from "mongoose"; +import type { Types, Model } from "mongoose"; + +// Interface definition for a file document export interface InterfaceFile { _id: Types.ObjectId; - name: string; - url: string | undefined; - size: number | undefined; - secret: string; - contentType: string | undefined; - status: string; + fileName: string; + mimeType: string; + size: number; + hash: { + value: string; + algorithm: string; + }; + uri: string; + referenceCount: number; + metadata: Record; // eslint-disable-line @typescript-eslint/no-explicit-any + encryption: boolean; + archived: boolean; + visibility: "PRIVATE" | "PUBLIC"; + backupStatus: string; + status: "ACTIVE" | "BLOCKED" | "DELETED"; createdAt: Date; updatedAt: Date; + archivedAt?: Date; } /** - * Mongoose schema for a file. - * Defines the structure of the file document stored in MongoDB. - * @param name - The name of the file. - * @param url - The URL where the file is stored. + * Mongoose schema for the `File` collection. + * + * This schema defines the structure for storing files in the database, including details such as + * the file name, size, hash, URI, visibility, and status. It also includes metadata, encryption, and archival details. + * The schema automatically manages `createdAt` and `updatedAt` timestamps using Mongoose's `timestamps` option. + * + * @param fileName - The name of the file (defaults to a UUID if not provided). + * @param mimeType - The MIME type of the file (e.g., `image/png`). * @param size - The size of the file in bytes. - * @param secret - A secret key associated with the file. - * @param contentType - The MIME type of the file. - * @param status - The status of the file (e.g., ACTIVE, BLOCKED, DELETED). - * @param createdAt - The date and time when the file was created. - * @param updatedAt - The date and time when the file was last updated. + * @param hash - An object containing the hash value and the algorithm used for the hash. + * @param uri - The URI of the file location. + * @param referenceCount - The number of references to the file (defaults to 1). + * @param metadata - An object containing additional metadata for the file. + * @param encryption - Indicates whether the file is encrypted. + * @param archived - Indicates whether the file is archived. + * @param visibility - File visibility (`PRIVATE` or `PUBLIC`). + * @param backupStatus - The status of the file's backup. + * @param status - The current status of the file (`ACTIVE`, `BLOCKED`, or `DELETED`). + * @param createdAt - The timestamp when the file was created. + * @param updatedAt - The timestamp when the file was last updated. + * @param archivedAt - The timestamp when the file was archived (if applicable). + * + * @example + * ```typescript + * const newFile = new File({ + * fileName: "example.png", + * mimeType: "image/png", + * size: 2048, + * hash: { value: "abc123", algorithm: "sha256" }, + * uri: "/path/to/file", + * }); + * await newFile.save(); + * ``` */ -const fileSchema = new Schema( +const fileSchema = new Schema( { - name: { + fileName: { type: String, required: true, - default: uuidv4(), // Generates a unique identifier for the name by default + default: uuidv4(), }, - url: { + mimeType: { type: String, + required: true, }, size: { type: Number, + required: true, }, - secret: { + hash: { + value: { + type: String, + required: true, + }, + algorithm: { + type: String, + required: true, + }, + }, + uri: { type: String, required: true, }, - contentType: { + referenceCount: { + type: Number, + default: 1, + }, + metadata: { + type: Schema.Types.Mixed, + }, + archivedAt: { + type: Date, + }, + encryption: { + type: Boolean, + default: false, + }, + archived: { + type: Boolean, + default: false, + }, + visibility: { + type: String, + enum: ["PRIVATE", "PUBLIC"], + default: "PUBLIC", + }, + backupStatus: { type: String, }, status: { @@ -57,26 +124,27 @@ const fileSchema = new Schema( }, }, { - timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields + timestamps: true, }, ); -// Add logging middleware for fileSchema -createLoggingMiddleware(fileSchema, "File"); - /** - * Function to retrieve or create the Mongoose model for the File. - * This is necessary to avoid the OverwriteModelError during testing. - * @returns The Mongoose model for the File. + * Creates and exports the Mongoose `File` model. + * + * The `File` model interacts with the `File` collection in the MongoDB database. + * It allows you to create, retrieve, update, and delete file documents based on the defined schema. + * + * @returns The Mongoose model for the `File` collection. + * + * @example + * ```typescript + * const file = await File.findById(fileId); + * console.log(file); + * ``` */ const fileModel = (): Model => model("File", fileSchema); -/** - * The Mongoose model for the File. - * If the model already exists (e.g., during testing), it uses the existing model. - * Otherwise, it creates a new model. - */ export const File = (models.File || fileModel()) as ReturnType< typeof fileModel >; diff --git a/src/models/GroupChat.ts b/src/models/GroupChat.ts deleted file mode 100644 index 3ecf20f70d9..00000000000 --- a/src/models/GroupChat.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { Types, PopulatedDoc, Document, Model } from "mongoose"; -import { Schema, model, models } from "mongoose"; -import type { InterfaceGroupChatMessage } from "./GroupChatMessage"; -import type { InterfaceOrganization } from "./Organization"; -import type { InterfaceUser } from "./User"; -import { createLoggingMiddleware } from "../libraries/dbLogger"; - -/** - * Interface representing a document for a group chat in the database (MongoDB). - */ -export interface InterfaceGroupChat { - _id: Types.ObjectId; - title: string; - users: PopulatedDoc[]; - messages: PopulatedDoc[]; - creatorId: PopulatedDoc; - createdAt: Date; - updatedAt: Date; - organization: PopulatedDoc; - status: string; -} -/** - * Mongoose schema definition for a group chat document. - * Defines how group chat data will be stored in MongoDB. - * - * @param title - Title of the group chat. - * @param users - Users participating in the group chat. - * @param messages - Messages sent in the group chat. - * @param creatorId - Creator of the group chat. - * @param createdAt - Timestamp of creation - * @param updatedAt - Timestamp of updation - * @param organization - Organization associated with the group chat. - * @param status - Status of the group chat. - */ -const groupChatSchema = new Schema( - { - title: { - type: String, - required: true, - }, - users: [ - { - type: Schema.Types.ObjectId, - ref: "User", - required: true, // At least one user is required - }, - ], - messages: [ - { - type: Schema.Types.ObjectId, - ref: "GroupChatMessage", - }, - ], - creatorId: { - type: Schema.Types.ObjectId, - ref: "User", - required: true, - }, - organization: { - type: Schema.Types.ObjectId, - ref: "Organization", - required: true, - }, - status: { - type: String, - required: true, - enum: ["ACTIVE", "BLOCKED", "DELETED"], // Status must be one of these values - default: "ACTIVE", - }, - }, - { - timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields - }, -); - -/** - * Adds logging middleware to the group chat schema. - * Middleware logs changes to group chat documents. - */ -createLoggingMiddleware(groupChatSchema, "GroupChat"); - -/** - * Creates a Mongoose model for the group chat schema. - * Ensures that we don't create multiple models during testing, which can cause errors. - * - * @returns The GroupChat model. - */ -const groupChatModel = (): Model => - model("GroupChat", groupChatSchema); - -/** - * Export the GroupChat model. - * This syntax ensures we don't get an OverwriteModelError while running tests. - */ -export const GroupChat = (models.GroupChat || groupChatModel()) as ReturnType< - typeof groupChatModel ->; diff --git a/src/models/GroupChatMessage.ts b/src/models/GroupChatMessage.ts deleted file mode 100644 index f3b583bb2b5..00000000000 --- a/src/models/GroupChatMessage.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { Types, PopulatedDoc, Document, Model } from "mongoose"; -import { Schema, model, models } from "mongoose"; -import type { InterfaceGroupChat } from "./GroupChat"; -import type { InterfaceUser } from "./User"; -import { createLoggingMiddleware } from "../libraries/dbLogger"; - -/** - * Interface representing a document for a group chat message in the database (MongoDB). - */ -export interface InterfaceGroupChatMessage { - _id: Types.ObjectId; - groupChatMessageBelongsTo: PopulatedDoc; - sender: PopulatedDoc; - createdAt: Date; - updatedAt: Date; - messageContent: string; - status: string; -} - -/** - * Mongoose schema for a group chat message. - * Defines the structure of the group chat message document stored in MongoDB. - * @param groupChatMessageBelongsTo - The association referring to the GroupChat model. - * @param sender - The sender of the message. - * @param messageContent - The content of the message. - * @param status - The status of the message (e.g., ACTIVE, BLOCKED, DELETED). - * @param createdAt - The date and time when the message was created. - * @param updatedAt - The date and time when the message was last updated. - */ -const groupChatMessageSchema = new Schema( - { - groupChatMessageBelongsTo: { - type: Schema.Types.ObjectId, - ref: "GroupChat", - required: true, - }, - sender: { - type: Schema.Types.ObjectId, - ref: "User", - required: true, - }, - messageContent: { - type: String, - required: true, - }, - status: { - type: String, - required: true, - enum: ["ACTIVE", "BLOCKED", "DELETED"], - default: "ACTIVE", - }, - }, - { - timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields - }, -); - -// Add logging middleware for groupChatMessageSchema -createLoggingMiddleware(groupChatMessageSchema, "GroupChatMessage"); - -/** - * Function to retrieve or create the Mongoose model for the GroupChatMessage. - * This is necessary to avoid the OverwriteModelError during testing. - * @returns The Mongoose model for the GroupChatMessage. - */ -const groupChatMessageModel = (): Model => - model("GroupChatMessage", groupChatMessageSchema); - -/** - * The Mongoose model for the GroupChatMessage. - * If the model already exists (e.g., during testing), it uses the existing model. - * Otherwise, it creates a new model. - */ -export const GroupChatMessage = (models.GroupChatMessage || - groupChatMessageModel()) as ReturnType; diff --git a/src/models/IdentifierCount.ts b/src/models/IdentifierCount.ts new file mode 100644 index 00000000000..c20d2810c46 --- /dev/null +++ b/src/models/IdentifierCount.ts @@ -0,0 +1,46 @@ +import mongoose, { model, Schema } from "mongoose"; +import type { Model, Document } from "mongoose"; + +/** + * Interface representing an identifier document in MongoDB. + * Extends Mongoose's Document interface to include custom fields. + */ +interface InterfaceIdentifier extends Document { + /** + *@param _id - The unique identifier for the document. + */ + _id: string; + + /** + *@param sequence_value - The sequence value associated with the identifier. + */ + sequence_value: number; +} + +/** + * Schema definition for the identifier document. + */ +const identifierSchema = new Schema({ + /** + *@param _id - Must be a string and is required. + */ + _id: { type: String, required: true }, + + /** + *@param sequence_value - The sequence value associated with the identifier. Must be a number. + */ + sequence_value: { type: Number }, +}); + +/** + * Mongoose model for the identifier collection. + * Reuses the existing model if it already exists, or creates a new one. + */ +const lastIdentifier: Model = + mongoose.models.identifier_count || + model("identifier_count", identifierSchema); + +/** + * Export the Mongoose model for the identifier collection. + */ +export const identifier_count = lastIdentifier; diff --git a/src/models/MessageChat.ts b/src/models/MessageChat.ts deleted file mode 100644 index 7510ea8d1a5..00000000000 --- a/src/models/MessageChat.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { PopulatedDoc, Types, Document, Model } from "mongoose"; -import { Schema, model, models } from "mongoose"; -import type { InterfaceUser } from "./User"; -import { createLoggingMiddleware } from "../libraries/dbLogger"; -/** - * Interface representing a document for a chat in the database (MongoDB). - */ -export interface InterfaceMessageChat { - _id: Types.ObjectId; - message: string; - languageBarrier: boolean; - sender: PopulatedDoc; - receiver: PopulatedDoc; - createdAt: Date; - updatedAt: Date; -} -/** - * Mongoose schema for a Message Chat. - * Defines the structure of the Message Chat document stored in MongoDB. - * @param message - The content of the chat message. - * @param languageBarrier - Indicates if there's a language barrier in the chat. - * @param sender - Reference to the User who sent the chat message. - * @param receiver - Reference to the User who received the chat message. - * @param createdAt - The date and time when the chat was created. - * @param updatedAt - The date and time when the chat was last updated. - */ -const messageChatSchema = new Schema( - { - message: { - type: String, - required: true, - }, - languageBarrier: { - type: Boolean, - required: false, - default: false, - }, - sender: { - type: Schema.Types.ObjectId, - ref: "User", - required: true, - }, - receiver: { - type: Schema.Types.ObjectId, - ref: "User", - required: true, - }, - }, - { - timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields - }, -); - -// Add logging middleware for messageChatSchema -createLoggingMiddleware(messageChatSchema, "MessageChat"); - -/** - * Function to retrieve or create the Mongoose model for the MessageChat. - * This is necessary to avoid the OverwriteModelError during testing. - * @returns The Mongoose model for the MessageChat. - */ -const messageChatModel = (): Model => - model("MessageChat", messageChatSchema); - -/** - * The Mongoose model for the MessageChat. - * If the model already exists (e.g., during testing), it uses the existing model. - * Otherwise, it creates a new model. - */ -export const MessageChat = (models.MessageChat || - messageChatModel()) as ReturnType; diff --git a/src/models/OrganizationTagUser.ts b/src/models/OrganizationTagUser.ts index 467a500df57..62228951e54 100644 --- a/src/models/OrganizationTagUser.ts +++ b/src/models/OrganizationTagUser.ts @@ -45,10 +45,22 @@ const organizationTagUserSchema = new Schema({ }, }); -// Index to ensure unique combination of organizationId, parentOrganizationTagUserId, and name +// Define partial indexes to enforce the unique constraints +// two tags at the same level can't have the same name organizationTagUserSchema.index( - { organizationId: 1, parentOrganizationTagUserId: 1, name: 1 }, - { unique: true }, + { organizationId: 1, name: 1 }, + { + unique: true, + partialFilterExpression: { parentTagId: { $eq: null } }, + }, +); + +organizationTagUserSchema.index( + { organizationId: 1, parentTagId: 1, name: 1 }, + { + unique: true, + partialFilterExpression: { parentTagId: { $ne: null } }, + }, ); // Add logging middleware for organizationTagUserSchema diff --git a/src/models/Post.ts b/src/models/Post.ts index f3bd86a624c..209db8ba62f 100644 --- a/src/models/Post.ts +++ b/src/models/Post.ts @@ -3,6 +3,7 @@ import { Schema, model, models } from "mongoose"; import mongoosePaginate from "mongoose-paginate-v2"; import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; +import type { InterfaceFile } from "./File"; import { createLoggingMiddleware } from "../libraries/dbLogger"; /** @@ -13,7 +14,7 @@ export interface InterfacePost { commentCount: number; createdAt: Date; creatorId: PopulatedDoc; - imageUrl: string | undefined | null; + file: PopulatedDoc | null; likeCount: number; likedBy: PopulatedDoc[]; organization: PopulatedDoc; @@ -22,7 +23,6 @@ export interface InterfacePost { text: string; title: string | undefined; updatedAt: Date; - videoUrl: string | undefined | null; } /** @@ -54,13 +54,9 @@ const postSchema = new Schema( enum: ["ACTIVE", "BLOCKED", "DELETED"], default: "ACTIVE", }, - imageUrl: { - type: String, - required: false, - }, - videoUrl: { - type: String, - required: false, + file: { + type: Schema.Types.ObjectId, + ref: "File", }, creatorId: { type: Schema.Types.ObjectId, diff --git a/src/models/SampleData.ts b/src/models/SampleData.ts index 8f09cc609da..ea8c91a3e1b 100644 --- a/src/models/SampleData.ts +++ b/src/models/SampleData.ts @@ -11,6 +11,7 @@ export interface InterfaceSampleData extends Document { | "Organization" | "Post" | "Event" + | "Venue" | "User" | "Plugin" | "AppUserProfile"; @@ -28,7 +29,15 @@ const sampleDataSchema = new Schema({ collectionName: { type: String, required: true, - enum: ["Organization", "Post", "Event", "User", "AppUserProfile", "Plugin"], + enum: [ + "Organization", + "Post", + "Event", + "Venue", + "User", + "AppUserProfile", + "Plugin", + ], }, }); diff --git a/src/models/TagUser.ts b/src/models/TagUser.ts index cd2ccb8d0c1..fd7889ca35e 100644 --- a/src/models/TagUser.ts +++ b/src/models/TagUser.ts @@ -30,6 +30,11 @@ const tagUserSchema = new Schema({ ref: "OrganizationTagUser", required: true, }, + organizationId: { + type: Schema.Types.ObjectId, + ref: "Organization", + required: true, + }, tagColor: { type: String, required: false, diff --git a/src/models/User.ts b/src/models/User.ts index a2c22cc7c4d..990e131a63b 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -7,12 +7,14 @@ import type { InterfaceAppUserProfile } from "./AppUserProfile"; import type { InterfaceEvent } from "./Event"; import type { InterfaceMembershipRequest } from "./MembershipRequest"; import type { InterfaceOrganization } from "./Organization"; +import { identifier_count } from "./IdentifierCount"; /** * Represents a MongoDB document for User in the database. */ export interface InterfaceUser { _id: Types.ObjectId; + identifier: number; appUserProfileId: PopulatedDoc; address: { city: string; @@ -46,6 +48,7 @@ export interface InterfaceUser { mobile: string; work: string; }; + eventsAttended: PopulatedDoc[]; registeredEvents: PopulatedDoc[]; status: string; @@ -56,6 +59,7 @@ export interface InterfaceUser { /** * Mongoose schema definition for User documents. * @param appUserProfileId - Reference to the user's app profile. + * @param identifier - unique numeric identifier for each User * @param address - User's address details. * @param birthDate - User's date of birth. * @param createdAt - Timestamp of when the user was created. @@ -74,10 +78,17 @@ export interface InterfaceUser { * @param phone - User's contact numbers (home, mobile, work). * @param registeredEvents - Events the user has registered for. * @param status - User's status (ACTIVE, BLOCKED, DELETED). + * @param eventsAttended - Events the user has attended. * @param updatedAt - Timestamp of when the user was last updated. */ const userSchema = new Schema( { + identifier: { + type: Number, + unique: true, + required: true, + immutable: true, + }, appUserProfileId: { type: Schema.Types.ObjectId, ref: "AppUserProfile", @@ -211,6 +222,12 @@ const userSchema = new Schema( ref: "Event", }, ], + eventsAttended: [ + { + type: Schema.Types.ObjectId, + ref: "Event", + }, + ], status: { type: String, required: true, @@ -225,6 +242,19 @@ const userSchema = new Schema( userSchema.plugin(mongoosePaginate); +userSchema.pre("validate", async function (next) { + if (!this.identifier) { + const counter = await identifier_count.findOneAndUpdate( + { _id: "userCounter" }, + { $inc: { sequence_value: 1 } }, + { new: true, upsert: true }, + ); + + this.identifier = counter.sequence_value; + } + return next(); +}); + // Create and export the User model const userModel = (): PaginateModel => model>("User", userSchema); diff --git a/src/models/VolunteerMembership.ts b/src/models/VolunteerMembership.ts new file mode 100644 index 00000000000..3342129d10e --- /dev/null +++ b/src/models/VolunteerMembership.ts @@ -0,0 +1,94 @@ +import type { PopulatedDoc, Document, Model, Types } from "mongoose"; +import { Schema, model, models } from "mongoose"; +import type { InterfaceEvent } from "./Event"; +import type { InterfaceEventVolunteer } from "./EventVolunteer"; +import type { InterfaceEventVolunteerGroup } from "./EventVolunteerGroup"; +import { createLoggingMiddleware } from "../libraries/dbLogger"; +import type { InterfaceUser } from "./User"; + +/** + * Represents a document for a volunteer membership in the MongoDB database. + * This interface defines the structure and types of data that a volunteer membership document will hold. + */ +export interface InterfaceVolunteerMembership { + _id: Types.ObjectId; + volunteer: PopulatedDoc; + group: PopulatedDoc; + event: PopulatedDoc; + status: "invited" | "requested" | "accepted" | "rejected"; + createdBy: PopulatedDoc; + updatedBy: PopulatedDoc; + createdAt: Date; + updatedAt: Date; +} + +/** + * Mongoose schema definition for a volunteer group membership document. + * This schema defines how the data will be stored in the MongoDB database. + * + * @param volunteer - Reference to the event volunteer involved in the group membership. + * @param group - Reference to the event volunteer group. Absence denotes a request for individual volunteer request. + * @param event - Reference to the event that the group is part of. + * @param status - Current status of the membership (invited, requested, accepted, rejected). + * @param createdBy - Reference to the user who created the group membership document. + * @param updatedBy - Reference to the user who last updated the group membership document. + * @param createdAt - Timestamp of when the group membership document was created. + * @param updatedAt - Timestamp of when the group membership document was last updated. + */ +const volunteerMembershipSchema = new Schema( + { + volunteer: { + type: Schema.Types.ObjectId, + ref: "EventVolunteer", + required: true, + }, + group: { + type: Schema.Types.ObjectId, + ref: "EventVolunteerGroup", + }, + event: { + type: Schema.Types.ObjectId, + ref: "Event", + required: true, + }, + status: { + type: String, + enum: ["invited", "requested", "accepted", "rejected"], + required: true, + default: "invited", + }, + createdBy: { + type: Schema.Types.ObjectId, + ref: "User", + }, + updatedBy: { + type: Schema.Types.ObjectId, + ref: "User", + }, + }, + { + timestamps: true, // Automatically manage `createdAt` and `updatedAt` fields + }, +); + +// Enable logging on changes in VolunteerMembership collection +createLoggingMiddleware(volunteerMembershipSchema, "VolunteerMembership"); + +/** + * Creates a Mongoose model for the volunteer group membership schema. + * This function ensures that we don't create multiple models during testing, which can cause errors. + * + * @returns The VolunteerMembership model. + */ +const volunteerMembershipModel = (): Model => + model( + "VolunteerMembership", + volunteerMembershipSchema, + ); + +/** + * Export the VolunteerMembership model. + * This syntax ensures we don't get an OverwriteModelError while running tests. + */ +export const VolunteerMembership = (models.VolunteerMembership || + volunteerMembershipModel()) as ReturnType; diff --git a/src/models/index.ts b/src/models/index.ts index 9ce9aed7e71..2da94814120 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -9,8 +9,6 @@ export * from "./CheckIn"; export * from "./CheckOut"; export * from "./Comment"; export * from "./Community"; -export * from "./DirectChat"; -export * from "./DirectChatMessage"; export * from "./Donation"; export * from "./Event"; export * from "./EventAttendee"; @@ -21,13 +19,10 @@ export * from "./File"; export * from "./Fund"; export * from "./FundraisingCampaign"; export * from "./Group"; -export * from "./GroupChat"; -export * from "./GroupChatMessage"; export * from "./ImageHash"; export * from "./Language"; export * from "./MembershipRequest"; export * from "./Message"; -export * from "./MessageChat"; export * from "./Organization"; export * from "./OrganizationCustomField"; export * from "./OrganizationTagUser"; @@ -38,5 +33,8 @@ export * from "./RecurrenceRule"; export * from "./SampleData"; export * from "./TagUser"; export * from "./Venue"; +export * from "./VolunteerMembership"; export * from "./User"; export * from "./Note"; +export * from "./Chat"; +export * from "./ChatMessage"; diff --git a/src/resolvers/Chat/admins.ts b/src/resolvers/Chat/admins.ts new file mode 100644 index 00000000000..a1028aa4e21 --- /dev/null +++ b/src/resolvers/Chat/admins.ts @@ -0,0 +1,14 @@ +import { User } from "../../models"; +import type { ChatResolvers } from "../../types/generatedGraphQLTypes"; +/** + * This resolver function will fetch and return the list of all chat admins from the database. + * @param parent - An object that is the return value of the resolver for this field's parent. + * @returns An `object` that contains the list of users. + */ +export const admins: ChatResolvers["users"] = async (parent) => { + return await User.find({ + _id: { + $in: parent.users, + }, + }).lean(); +}; diff --git a/src/resolvers/DirectChat/creator.ts b/src/resolvers/Chat/creator.ts similarity index 51% rename from src/resolvers/DirectChat/creator.ts rename to src/resolvers/Chat/creator.ts index 02fed5c4a9b..8d76e1ec3bf 100644 --- a/src/resolvers/DirectChat/creator.ts +++ b/src/resolvers/Chat/creator.ts @@ -1,11 +1,11 @@ import { User } from "../../models"; -import type { DirectChatResolvers } from "../../types/generatedGraphQLTypes"; +import type { ChatResolvers } from "../../types/generatedGraphQLTypes"; /** - * This resolver function will fetch and return the specified Direct Chat User from database. + * This resolver function will fetch and return the specified Chat User from database. * @param parent - An object that is the return value of the resolver for this field's parent. * @returns An `object` that contains the User data. */ -export const creator: DirectChatResolvers["creator"] = async (parent) => { +export const creator: ChatResolvers["creator"] = async (parent) => { return await User.findOne({ _id: parent.creatorId, }).lean(); diff --git a/src/resolvers/GroupChat/index.ts b/src/resolvers/Chat/index.ts similarity index 57% rename from src/resolvers/GroupChat/index.ts rename to src/resolvers/Chat/index.ts index 9c815600150..d4559ae213b 100644 --- a/src/resolvers/GroupChat/index.ts +++ b/src/resolvers/Chat/index.ts @@ -1,12 +1,14 @@ -import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; +import type { ChatResolvers } from "../../types/generatedGraphQLTypes"; import { creator } from "./creator"; import { messages } from "./messages"; import { organization } from "./organization"; import { users } from "./users"; +import { admins } from "./admins"; -export const GroupChat: GroupChatResolvers = { +export const Chat: ChatResolvers = { creator, messages, organization, users, + admins, }; diff --git a/src/resolvers/Chat/messages.ts b/src/resolvers/Chat/messages.ts new file mode 100644 index 00000000000..db16a74708d --- /dev/null +++ b/src/resolvers/Chat/messages.ts @@ -0,0 +1,14 @@ +import { ChatMessage } from "../../models"; +import type { ChatResolvers } from "../../types/generatedGraphQLTypes"; +/** + * This resolver function will fetch and return the list of all messages in specified Chat from database. + * @param parent - An object that is the return value of the resolver for this field's parent. + * @returns An `object` that contains the list of messages. + */ +export const messages: ChatResolvers["messages"] = async (parent) => { + return await ChatMessage.find({ + _id: { + $in: parent.messages, + }, + }).lean(); +}; diff --git a/src/resolvers/DirectChat/organization.ts b/src/resolvers/Chat/organization.ts similarity index 82% rename from src/resolvers/DirectChat/organization.ts rename to src/resolvers/Chat/organization.ts index 9073af15f44..b5692d9392b 100644 --- a/src/resolvers/DirectChat/organization.ts +++ b/src/resolvers/Chat/organization.ts @@ -2,15 +2,13 @@ import type { InterfaceOrganization } from "../../models"; import { Organization } from "../../models"; import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -import type { DirectChatResolvers } from "../../types/generatedGraphQLTypes"; +import type { ChatResolvers } from "../../types/generatedGraphQLTypes"; /** - * This resolver function will fetch and return the Organization for the Direct Chat from database. + * This resolver function will fetch and return the Organization for the Chat from database. * @param parent - An object that is the return value of the resolver for this field's parent. * @returns An `object` that contains Organization data. */ -export const organization: DirectChatResolvers["organization"] = async ( - parent, -) => { +export const organization: ChatResolvers["organization"] = async (parent) => { const organizationFoundInCache = await findOrganizationsInCache([ parent.organization, ]); diff --git a/src/resolvers/DirectChat/users.ts b/src/resolvers/Chat/users.ts similarity index 65% rename from src/resolvers/DirectChat/users.ts rename to src/resolvers/Chat/users.ts index ebb3893c7fc..3912780779e 100644 --- a/src/resolvers/DirectChat/users.ts +++ b/src/resolvers/Chat/users.ts @@ -1,11 +1,11 @@ import { User } from "../../models"; -import type { DirectChatResolvers } from "../../types/generatedGraphQLTypes"; +import type { ChatResolvers } from "../../types/generatedGraphQLTypes"; /** - * This resolver function will fetch and return the list of all direct chat users from the database. + * This resolver function will fetch and return the list of all chat users from the database. * @param parent - An object that is the return value of the resolver for this field's parent. * @returns An `object` that contains the list of users. */ -export const users: DirectChatResolvers["users"] = async (parent) => { +export const users: ChatResolvers["users"] = async (parent) => { return await User.find({ _id: { $in: parent.users, diff --git a/src/resolvers/ChatMessage/chatMessageBelongsTo.ts b/src/resolvers/ChatMessage/chatMessageBelongsTo.ts new file mode 100644 index 00000000000..bdd5277b6b6 --- /dev/null +++ b/src/resolvers/ChatMessage/chatMessageBelongsTo.ts @@ -0,0 +1,25 @@ +import type { ChatMessageResolvers } from "../../types/generatedGraphQLTypes"; +import { Chat } from "../../models"; +import { CHAT_NOT_FOUND_ERROR } from "../../constants"; +import { errors, requestContext } from "../../libraries"; +/** + * This resolver method will retrieve and return from the database the Chat to which the specified message belongs. + * @param parent - An object that is the return value of the resolver for this field's parent. + * @returns An `object` that contains the Chat data. + */ +export const chatMessageBelongsTo: ChatMessageResolvers["chatMessageBelongsTo"] = + async (parent) => { + const chatResult = await Chat.findOne({ + _id: parent.chatMessageBelongsTo, + }).lean(); + + if (chatResult) { + return chatResult; + } else { + throw new errors.NotFoundError( + requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), + CHAT_NOT_FOUND_ERROR.CODE, + CHAT_NOT_FOUND_ERROR.PARAM, + ); + } + }; diff --git a/src/resolvers/ChatMessage/index.ts b/src/resolvers/ChatMessage/index.ts new file mode 100644 index 00000000000..3b1d0e60f0e --- /dev/null +++ b/src/resolvers/ChatMessage/index.ts @@ -0,0 +1,10 @@ +import type { ChatMessageResolvers } from "../../types/generatedGraphQLTypes"; +import { chatMessageBelongsTo } from "./chatMessageBelongsTo"; +import { replyTo } from "./replyTo"; +import { sender } from "./sender"; + +export const ChatMessage: ChatMessageResolvers = { + chatMessageBelongsTo, + sender, + replyTo, +}; diff --git a/src/resolvers/ChatMessage/replyTo.ts b/src/resolvers/ChatMessage/replyTo.ts new file mode 100644 index 00000000000..ff2dc872bbd --- /dev/null +++ b/src/resolvers/ChatMessage/replyTo.ts @@ -0,0 +1,28 @@ +import type { ChatMessageResolvers } from "../../types/generatedGraphQLTypes"; +import { ChatMessage } from "../../models"; +import { MESSAGE_NOT_FOUND_ERROR } from "../../constants"; +import { errors, requestContext } from "../../libraries"; +/** + * This resolver function will fetch and return the message replied to specific to the chat from the database. + * @param parent - An object that is the return value of the resolver for this field's parent. + * @returns An `object` that contains reply Message's data. + */ +export const replyTo: ChatMessageResolvers["replyTo"] = async (parent) => { + if (parent.replyTo) { + const result = await ChatMessage.findOne({ + _id: parent.replyTo, + }).lean(); + + if (result) { + return result; + } else { + throw new errors.NotFoundError( + requestContext.translate(MESSAGE_NOT_FOUND_ERROR.MESSAGE), + MESSAGE_NOT_FOUND_ERROR.CODE, + MESSAGE_NOT_FOUND_ERROR.PARAM, + ); + } + } else { + return null; + } +}; diff --git a/src/resolvers/ChatMessage/sender.ts b/src/resolvers/ChatMessage/sender.ts new file mode 100644 index 00000000000..c318fdd7ae3 --- /dev/null +++ b/src/resolvers/ChatMessage/sender.ts @@ -0,0 +1,24 @@ +import type { ChatMessageResolvers } from "../../types/generatedGraphQLTypes"; +import { User } from "../../models"; +import { USER_NOT_FOUND_ERROR } from "../../constants"; +import { errors, requestContext } from "../../libraries"; +/** + * This resolver function will fetch and return the sender(user) of the Chat from the database. + * @param parent - An object that is the return value of the resolver for this field's parent. + * @returns An `object` that contains User's data. + */ +export const sender: ChatMessageResolvers["sender"] = async (parent) => { + const result = await User.findOne({ + _id: parent.sender, + }).lean(); + + if (result) { + return result; + } else { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } +}; diff --git a/src/resolvers/DirectChat/index.ts b/src/resolvers/DirectChat/index.ts deleted file mode 100644 index 8a0f6a4059a..00000000000 --- a/src/resolvers/DirectChat/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { DirectChatResolvers } from "../../types/generatedGraphQLTypes"; -import { creator } from "./creator"; -import { messages } from "./messages"; -import { organization } from "./organization"; -import { users } from "./users"; - -/** - * Resolver function for the `DirectChat` type. - * - * This resolver is used to resolve the fields of a `DirectChat` type. - * - * @see users - The resolver function for the `users` field of a `DirectChat`. - * @see organization - The resolver function for the `organization` field of a `DirectChat`. - * @see messages - The resolver function for the `messages` field of a `DirectChat`. - * @see creator - The resolver function for the `creator` field of a `DirectChat`. - * @see DirectChatResolvers - The type definition for the resolvers of the DirectChat fields. - * - */ -export const DirectChat: DirectChatResolvers = { - creator, - messages, - organization, - users, -}; diff --git a/src/resolvers/DirectChat/messages.ts b/src/resolvers/DirectChat/messages.ts deleted file mode 100644 index 3a3332ff88f..00000000000 --- a/src/resolvers/DirectChat/messages.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DirectChatMessage } from "../../models"; -import type { DirectChatResolvers } from "../../types/generatedGraphQLTypes"; -/** - * This resolver function will fetch and return the list of all messages in specified Direct Chat from database. - * @param parent - An object that is the return value of the resolver for this field's parent. - * @returns An `object` that contains the list of messages. - */ -export const messages: DirectChatResolvers["messages"] = async (parent) => { - return await DirectChatMessage.find({ - _id: { - $in: parent.messages, - }, - }).lean(); -}; diff --git a/src/resolvers/DirectChatMessage/directChatMessageBelongsTo.ts b/src/resolvers/DirectChatMessage/directChatMessageBelongsTo.ts deleted file mode 100644 index eb6a0d4d322..00000000000 --- a/src/resolvers/DirectChatMessage/directChatMessageBelongsTo.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { DirectChatMessageResolvers } from "../../types/generatedGraphQLTypes"; -import { DirectChat } from "../../models"; -import { CHAT_NOT_FOUND_ERROR } from "../../constants"; -import { errors, requestContext } from "../../libraries"; - -/** - * Resolver function for the `directChatMessageBelongsTo` field of a `DirectChatMessage`. - * - * This function retrieves the direct chat to which a specific direct chat message belongs. - * - * @param parent - The parent object representing the direct chat message. It contains information about the direct chat message, including the ID of the direct chat to which it belongs. - * @returns A promise that resolves to the direct chat document found in the database. This document represents the direct chat to which the direct chat message belongs. - * - * @see DirectChat - The DirectChat model used to interact with the direct chats collection in the database. - * @see DirectChatMessageResolvers - The type definition for the resolvers of the DirectChatMessage fields. - * - */ -export const directChatMessageBelongsTo: DirectChatMessageResolvers["directChatMessageBelongsTo"] = - async (parent) => { - const directChatResult = await DirectChat.findOne({ - _id: parent.directChatMessageBelongsTo, - }).lean(); - - if (directChatResult) { - return directChatResult; - } else { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - }; diff --git a/src/resolvers/DirectChatMessage/index.ts b/src/resolvers/DirectChatMessage/index.ts deleted file mode 100644 index e42390c4ec0..00000000000 --- a/src/resolvers/DirectChatMessage/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { DirectChatMessageResolvers } from "../../types/generatedGraphQLTypes"; -import { directChatMessageBelongsTo } from "./directChatMessageBelongsTo"; -import { receiver } from "./receiver"; -import { sender } from "./sender"; - -export const DirectChatMessage: DirectChatMessageResolvers = { - directChatMessageBelongsTo, - receiver, - sender, -}; diff --git a/src/resolvers/DirectChatMessage/receiver.ts b/src/resolvers/DirectChatMessage/receiver.ts deleted file mode 100644 index 1702f33b667..00000000000 --- a/src/resolvers/DirectChatMessage/receiver.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { DirectChatMessageResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; -import { USER_NOT_FOUND_ERROR } from "../../constants"; -import { errors, requestContext } from "../../libraries"; - -/** - * Resolver function for the `receiver` field of a `DirectChatMessage`. - * - * This function retrieves the user who received a specific direct chat message. - * - * @param parent - The parent object representing the direct chat message. It contains information about the direct chat message, including the ID of the user who received it. - * @returns A promise that resolves to the user document found in the database. This document represents the user who received the direct chat message. - * - * @see User - The User model used to interact with the users collection in the database. - * @see DirectChatMessageResolvers - The type definition for the resolvers of the DirectChatMessage fields. - * - */ -export const receiver: DirectChatMessageResolvers["receiver"] = async ( - parent, -) => { - const result = await User.findOne({ - _id: parent.receiver, - }).lean(); - - if (result) { - return result; - } else { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } -}; diff --git a/src/resolvers/DirectChatMessage/sender.ts b/src/resolvers/DirectChatMessage/sender.ts deleted file mode 100644 index 9a20bb44905..00000000000 --- a/src/resolvers/DirectChatMessage/sender.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { DirectChatMessageResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; -import { USER_NOT_FOUND_ERROR } from "../../constants"; -import { errors, requestContext } from "../../libraries"; - -/** - * Resolver function for the `sender` field of a `DirectChatMessage`. - * - * This function retrieves the user who sent a specific direct chat message. - * - * @param parent - The parent object representing the direct chat message. It contains information about the direct chat message, including the ID of the user who sent it. - * @returns A promise that resolves to the user document found in the database. This document represents the user who sent the direct chat message. - * - * @see User - The User model used to interact with the users collection in the database. - * @see DirectChatMessageResolvers - The type definition for the resolvers of the DirectChatMessage fields. - * - */ -export const sender: DirectChatMessageResolvers["sender"] = async (parent) => { - const result = await User.findOne({ - _id: parent.sender, - }).lean(); - - if (result) { - return result; - } else { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } -}; diff --git a/src/resolvers/Event/actionItems.ts b/src/resolvers/Event/actionItems.ts index b40c8a703c7..5099572fde1 100644 --- a/src/resolvers/Event/actionItems.ts +++ b/src/resolvers/Event/actionItems.ts @@ -15,6 +15,6 @@ import type { EventResolvers } from "../../types/generatedGraphQLTypes"; */ export const actionItems: EventResolvers["actionItems"] = async (parent) => { return await ActionItem.find({ - eventId: parent._id, + event: parent._id, }).lean(); }; diff --git a/src/resolvers/EventVolunteer/creator.ts b/src/resolvers/EventVolunteer/creator.ts deleted file mode 100644 index 59d13f2b574..00000000000 --- a/src/resolvers/EventVolunteer/creator.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { User } from "../../models"; -import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Resolver function for the `creator` field of an `EventVolunteer`. - * - * This function retrieves the user who created a specific event volunteer. - * - * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the user who created it. - * @returns A promise that resolves to the user document found in the database. This document represents the user who created the event volunteer. - * - * @see User - The User model used to interact with the users collection in the database. - * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. - * - */ -export const creator: EventVolunteerResolvers["creator"] = async (parent) => { - return await User.findOne({ - _id: parent.creatorId, - }).lean(); -}; diff --git a/src/resolvers/EventVolunteer/event.ts b/src/resolvers/EventVolunteer/event.ts deleted file mode 100644 index 438754aa911..00000000000 --- a/src/resolvers/EventVolunteer/event.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Event } from "../../models"; -import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Resolver function for the `event` field of an `EventVolunteer`. - * - * This function retrieves the event associated with a specific event volunteer. - * - * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the event associated with it. - * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the event volunteer. - * - * @see Event - The Event model used to interact with the events collection in the database. - * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. - * - */ -export const event: EventVolunteerResolvers["event"] = async (parent) => { - return await Event.findOne({ - _id: parent.eventId, - }).lean(); -}; diff --git a/src/resolvers/EventVolunteer/group.ts b/src/resolvers/EventVolunteer/group.ts deleted file mode 100644 index 4fa94b9ee63..00000000000 --- a/src/resolvers/EventVolunteer/group.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { EventVolunteerGroup } from "../../models"; -import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Resolver function for the `group` field of an `EventVolunteer`. - * - * This function retrieves the group associated with a specific event volunteer. - * - * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the group associated with it. - * @returns A promise that resolves to the group document found in the database. This document represents the group associated with the event volunteer. - * - * @see EventVolunteerGroup - The EventVolunteerGroup model used to interact with the event volunteer groups collection in the database. - * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. - * - */ -export const group: EventVolunteerResolvers["group"] = async (parent) => { - return await EventVolunteerGroup.findOne({ - _id: parent.groupId, - }).lean(); -}; diff --git a/src/resolvers/EventVolunteer/index.ts b/src/resolvers/EventVolunteer/index.ts deleted file mode 100644 index 108e57c7125..00000000000 --- a/src/resolvers/EventVolunteer/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; -import { event } from "./event"; -import { creator } from "./creator"; -import { user } from "./user"; -import { group } from "./group"; - -export const EventVolunteer: EventVolunteerResolvers = { - creator, - event, - group, - user, -}; diff --git a/src/resolvers/EventVolunteer/user.ts b/src/resolvers/EventVolunteer/user.ts deleted file mode 100644 index 5059bd1dcbc..00000000000 --- a/src/resolvers/EventVolunteer/user.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { User } from "../../models"; -import type { EventVolunteerResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Resolver function for the `user` field of an `EventVolunteer`. - * - * This function retrieves the user who created a specific event volunteer. - * - * @param parent - The parent object representing the event volunteer. It contains information about the event volunteer, including the ID of the user who created it. - * @returns A promise that resolves to the user document found in the database. This document represents the user who created the event volunteer. - * - * @see User - The User model used to interact with the users collection in the database. - * @see EventVolunteerResolvers - The type definition for the resolvers of the EventVolunteer fields. - * - */ -export const user: EventVolunteerResolvers["user"] = async (parent) => { - const result = await User.findOne({ - _id: parent.userId, - }).lean(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return result!; -}; diff --git a/src/resolvers/EventVolunteerGroup/creator.ts b/src/resolvers/EventVolunteerGroup/creator.ts deleted file mode 100644 index 7924de21157..00000000000 --- a/src/resolvers/EventVolunteerGroup/creator.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { User } from "../../models"; -import type { EventVolunteerGroupResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Resolver function for the `creator` field of an `EventVolunteerGroup`. - * - * This function retrieves the user who created a specific event volunteer group. - * - * @param parent - The parent object representing the event volunteer group. It contains information about the event volunteer group, including the ID of the user who created it. - * @returns A promise that resolves to the user document found in the database. This document represents the user who created the event volunteer group. - * - * @see User - The User model used to interact with the users collection in the database. - * @see EventVolunteerGroupResolvers - The type definition for the resolvers of the EventVolunteerGroup fields. - * - */ -export const creator: EventVolunteerGroupResolvers["creator"] = async ( - parent, -) => { - return await User.findOne({ - _id: parent.creatorId, - }).lean(); -}; diff --git a/src/resolvers/EventVolunteerGroup/event.ts b/src/resolvers/EventVolunteerGroup/event.ts deleted file mode 100644 index b758191d0a8..00000000000 --- a/src/resolvers/EventVolunteerGroup/event.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Event } from "../../models"; -import type { EventVolunteerGroupResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Resolver function for the `event` field of an `EventVolunteerGroup`. - * - * This function retrieves the event associated with a specific event volunteer group. - * - * @param parent - The parent object representing the event volunteer group. It contains information about the event volunteer group, including the ID of the event associated with it. - * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the event volunteer group. - * - * @see Event - The Event model used to interact with the events collection in the database. - * @see EventVolunteerGroupResolvers - The type definition for the resolvers of the EventVolunteerGroup fields. - * - */ -export const event: EventVolunteerGroupResolvers["event"] = async (parent) => { - return await Event.findOne({ - _id: parent.eventId, - }).lean(); -}; diff --git a/src/resolvers/EventVolunteerGroup/index.ts b/src/resolvers/EventVolunteerGroup/index.ts deleted file mode 100644 index 9c34f295603..00000000000 --- a/src/resolvers/EventVolunteerGroup/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { EventVolunteerGroupResolvers } from "../../types/generatedGraphQLTypes"; -import { leader } from "./leader"; -import { creator } from "./creator"; -import { event } from "./event"; - -export const EventVolunteerGroup: EventVolunteerGroupResolvers = { - creator, - leader, - event, -}; diff --git a/src/resolvers/EventVolunteerGroup/leader.ts b/src/resolvers/EventVolunteerGroup/leader.ts deleted file mode 100644 index 93a47b3eab6..00000000000 --- a/src/resolvers/EventVolunteerGroup/leader.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { User } from "../../models"; -import type { InterfaceUser } from "../../models"; -import type { EventVolunteerGroupResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Resolver function for the `leader` field of an `EventVolunteerGroup`. - * - * This function retrieves the user who is the leader of a specific event volunteer group. - * - * @param parent - The parent object representing the event volunteer group. It contains information about the event volunteer group, including the ID of the user who is the leader. - * @returns A promise that resolves to the user document found in the database. This document represents the user who is the leader of the event volunteer group. - * - * @see User - The User model used to interact with the users collection in the database. - * @see EventVolunteerGroupResolvers - The type definition for the resolvers of the EventVolunteerGroup fields. - * - */ -export const leader: EventVolunteerGroupResolvers["leader"] = async ( - parent, -) => { - const groupLeader = await User.findOne({ - _id: parent.leaderId, - }).lean(); - return groupLeader as InterfaceUser; -}; diff --git a/src/resolvers/GroupChat/creator.ts b/src/resolvers/GroupChat/creator.ts deleted file mode 100644 index ff7ea2cd51e..00000000000 --- a/src/resolvers/GroupChat/creator.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; - -/** - * Resolver function for the `creator` field of a `GroupChat`. - * - * This function retrieves the user who created a specific group chat. - * - * @param parent - The parent object representing the group chat. It contains information about the group chat, including the ID of the user who created it. - * @returns A promise that resolves to the user document found in the database. This document represents the user who created the group chat. - * - * @see User - The User model used to interact with the users collection in the database. - * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. - * - */ -export const creator: GroupChatResolvers["creator"] = async (parent) => { - return await User.findOne({ - _id: parent.creatorId, - }).lean(); -}; diff --git a/src/resolvers/GroupChat/messages.ts b/src/resolvers/GroupChat/messages.ts deleted file mode 100644 index cb03954b94f..00000000000 --- a/src/resolvers/GroupChat/messages.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; -import { GroupChatMessage } from "../../models"; - -/** - * Resolver function for the `messages` field of a `GroupChat`. - * - * This function retrieves the messages associated with a specific group chat. - * - * @param parent - The parent object representing the group chat. It contains information about the group chat, including the IDs of the messages associated with it. - * @returns A promise that resolves to the message documents found in the database. These documents represent the messages associated with the group chat. - * - * @see GroupChatMessage - The GroupChatMessage model used to interact with the group chat messages collection in the database. - * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. - * - */ -export const messages: GroupChatResolvers["messages"] = async (parent) => { - return await GroupChatMessage.find({ - _id: { - $in: parent.messages, - }, - }).lean(); -}; diff --git a/src/resolvers/GroupChat/organization.ts b/src/resolvers/GroupChat/organization.ts deleted file mode 100644 index ab83f0f3b3d..00000000000 --- a/src/resolvers/GroupChat/organization.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; -import { Organization } from "../../models"; -import type { InterfaceOrganization } from "../../models"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; - -/** - * Resolver function for the `organization` field of a `GroupChat`. - * - * This function retrieves the organization associated with a specific group chat. - * - * @param parent - The parent object representing the group chat. It contains information about the group chat, including the ID of the organization it is associated with. - * @returns A promise that resolves to the organization document found in the database. This document represents the organization associated with the group chat. - * - * @see Organization - The Organization model used to interact with the organizations collection in the database. - * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. - * - */ -export const organization: GroupChatResolvers["organization"] = async ( - parent, -) => { - const organizationFoundInCache = await findOrganizationsInCache([ - parent.organization, - ]); - - if (!organizationFoundInCache.includes(null)) { - return organizationFoundInCache[0] as InterfaceOrganization; - } - - const organization = await Organization.findOne({ - _id: parent.organization, - }).lean(); - if (organization) await cacheOrganizations([organization]); - - return organization as InterfaceOrganization; -}; diff --git a/src/resolvers/GroupChat/users.ts b/src/resolvers/GroupChat/users.ts deleted file mode 100644 index b4e0f363e19..00000000000 --- a/src/resolvers/GroupChat/users.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { GroupChatResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; - -/** - * Resolver function for the `users` field of a `GroupChat`. - * - * This function retrieves the users who are members of a specific group chat. - * - * @param parent - The parent object representing the group chat. It contains information about the group chat, including the IDs of the users who are members of it. - * @returns A promise that resolves to the user documents found in the database. These documents represent the users who are members of the group chat. - * - * @see User - The User model used to interact with the users collection in the database. - * @see GroupChatResolvers - The type definition for the resolvers of the GroupChat fields. - * - */ -export const users: GroupChatResolvers["users"] = async (parent) => { - return await User.find({ - _id: { - $in: parent.users, - }, - }).lean(); -}; diff --git a/src/resolvers/GroupChatMessage/groupChatMessageBelongsTo.ts b/src/resolvers/GroupChatMessage/groupChatMessageBelongsTo.ts deleted file mode 100644 index c9e2ec6a089..00000000000 --- a/src/resolvers/GroupChatMessage/groupChatMessageBelongsTo.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { GroupChatMessageResolvers } from "../../types/generatedGraphQLTypes"; -import { GroupChat } from "../../models"; -import { errors, requestContext } from "../../libraries"; -import { CHAT_NOT_FOUND_ERROR } from "../../constants"; - -/** - * Resolver function for the `groupChatMessageBelongsTo` field of a `GroupChatMessage`. - * - * This function retrieves the group chat to which a specific group chat message belongs. - * - * @param parent - The parent object representing the group chat message. It contains information about the group chat message, including the ID of the group chat to which it belongs. - * @returns A promise that resolves to the group chat document found in the database. This document represents the group chat to which the group chat message belongs. - * - * @see GroupChat - The GroupChat model used to interact with the group chats collection in the database. - * @see GroupChatMessageResolvers - The type definition for the resolvers of the GroupChatMessage fields. - * - */ -export const groupChatMessageBelongsTo: GroupChatMessageResolvers["groupChatMessageBelongsTo"] = - async (parent) => { - const result = await GroupChat.findOne({ - _id: parent.groupChatMessageBelongsTo, - }).lean(); - - if (result) { - return result; - } else { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - }; diff --git a/src/resolvers/GroupChatMessage/index.ts b/src/resolvers/GroupChatMessage/index.ts deleted file mode 100644 index b837e048716..00000000000 --- a/src/resolvers/GroupChatMessage/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { GroupChatMessageResolvers } from "../../types/generatedGraphQLTypes"; -import { groupChatMessageBelongsTo } from "./groupChatMessageBelongsTo"; -import { sender } from "./sender"; - -export const GroupChatMessage: GroupChatMessageResolvers = { - groupChatMessageBelongsTo, - sender, -}; diff --git a/src/resolvers/GroupChatMessage/sender.ts b/src/resolvers/GroupChatMessage/sender.ts deleted file mode 100644 index 9497a05f232..00000000000 --- a/src/resolvers/GroupChatMessage/sender.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { GroupChatMessageResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; -import { USER_NOT_FOUND_ERROR } from "../../constants"; -import { errors, requestContext } from "../../libraries"; - -/** - * Resolver function for the `sender` field of a `GroupChatMessage`. - * - * This function retrieves the user who sent a specific group chat message. - * - * @param parent - The parent object representing the group chat message. It contains information about the group chat message, including the ID of the user who sent it. - * @returns A promise that resolves to the user document found in the database. This document represents the user who sent the group chat message. - * - * @see User - The User model used to interact with the users collection in the database. - * @see GroupChatMessageResolvers - The type definition for the resolvers of the GroupChatMessage fields. - * - */ -export const sender: GroupChatMessageResolvers["sender"] = async (parent) => { - const result = await User.findOne({ - _id: parent.sender, - }).lean(); - - if (result) { - return result; - } else { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } -}; diff --git a/src/resolvers/Mutation/addEventAttendee.ts b/src/resolvers/Mutation/addEventAttendee.ts index 8c4242c484d..d683c2047e0 100644 --- a/src/resolvers/Mutation/addEventAttendee.ts +++ b/src/resolvers/Mutation/addEventAttendee.ts @@ -172,5 +172,23 @@ export const addEventAttendee: MutationResolvers["addEventAttendee"] = async ( await EventAttendee.create({ ...args.data }); - return requestUser; + const updatedUser = await User.findByIdAndUpdate( + args.data.userId, + { + $push: { + eventsAttended: args.data.eventId, + }, + }, + { new: true }, + ); + + if (updatedUser === null) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + return updatedUser; }; diff --git a/src/resolvers/Mutation/addPeopleToUserTag.ts b/src/resolvers/Mutation/addPeopleToUserTag.ts new file mode 100644 index 00000000000..4be0fd68f16 --- /dev/null +++ b/src/resolvers/Mutation/addPeopleToUserTag.ts @@ -0,0 +1,208 @@ +import { Types } from "mongoose"; +import { + TAG_NOT_FOUND, + USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../constants"; +import { errors, requestContext } from "../../libraries"; +import type { InterfaceAppUserProfile, InterfaceUser } from "../../models"; +import { + AppUserProfile, + OrganizationTagUser, + TagUser, + User, +} from "../../models"; +import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; +import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; +import { cacheUsers } from "../../services/UserCache/cacheUser"; +import { findUserInCache } from "../../services/UserCache/findUserInCache"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + +/** + * This function enables an admin to assign a tag to multiple users. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire application + * @remarks The following checks are done: + * 1. If the current user exists and has a profile. + * 2. If the tag object exists. + * 3. If the current user is an admin for the organization of the tag. + * 4. If each user to be assigned the tag exists and belongs to the tag's organization. + * 5. Assign the tag only to users who do not already have it. + * @returns Array of users to whom the tag was assigned. + */ + +export const addPeopleToUserTag: MutationResolvers["addPeopleToUserTag"] = + async (_parent, args, context) => { + let currentUser: InterfaceUser | null; + const userFoundInCache = await findUserInCache([context.userId]); + currentUser = userFoundInCache[0]; + if (currentUser === null) { + currentUser = await User.findOne({ + _id: context.userId, + }).lean(); + if (currentUser !== null) { + await cacheUsers([currentUser]); + } + } + + // Checks whether the currentUser exists. + if (!currentUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + // Get the tag object + const tag = await OrganizationTagUser.findOne({ + _id: args.input.tagId, + }).lean(); + + if (!tag) { + throw new errors.NotFoundError( + requestContext.translate(TAG_NOT_FOUND.MESSAGE), + TAG_NOT_FOUND.CODE, + TAG_NOT_FOUND.PARAM, + ); + } + + let currentUserAppProfile: InterfaceAppUserProfile | null; + + const appUserProfileFoundInCache = await findAppUserProfileCache([ + currentUser.appUserProfileId.toString(), + ]); + + currentUserAppProfile = appUserProfileFoundInCache[0]; + if (currentUserAppProfile === null) { + currentUserAppProfile = await AppUserProfile.findOne({ + userId: currentUser._id, + }).lean(); + if (currentUserAppProfile !== null) { + await cacheAppUserProfile([currentUserAppProfile]); + } + } + if (!currentUserAppProfile) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Boolean to determine whether user is an admin of the organization of the tag. + const currentUserIsOrganizationAdmin = currentUserAppProfile.adminFor.some( + (orgId) => + orgId && + new Types.ObjectId(orgId.toString()).equals(tag.organizationId), + ); + //check whether current user can assign tag to users or not + if ( + !(currentUserIsOrganizationAdmin || currentUserAppProfile.isSuperAdmin) + ) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Check if the request users exist + const requestUsers = await User.find({ + _id: { $in: args.input.userIds }, + }).lean(); + + const requestUserMap = new Map( + requestUsers.map((user) => [user._id.toString(), user]), + ); + + // Validate each user to be assigned the tag + const validRequestUsers = []; + for (const userId of args.input.userIds) { + const requestUser = requestUserMap.get(userId); + + if (!requestUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + // Check that the user to which the tag is to be assigned is a member of the tag's organization + const requestUserBelongsToTagOrganization = + requestUser.joinedOrganizations.some((orgId) => + orgId.equals(tag.organizationId), + ); + + if (!requestUserBelongsToTagOrganization) { + throw new errors.UnauthorizedError( + requestContext.translate( + USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION.MESSAGE, + ), + USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION.CODE, + USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION.PARAM, + ); + } + + validRequestUsers.push(requestUser); + } + + // Check existing tag assignments + const existingTagAssignments = await TagUser.find({ + userId: { $in: validRequestUsers.map((user) => user._id) }, + tagId: tag._id, + }).lean(); + + const existingAssignmentsMap = existingTagAssignments.map((assign) => + assign.userId.toString(), + ); + + // Filter out users who already have the tag + const newAssignments = validRequestUsers.filter( + (user) => !existingAssignmentsMap.includes(user._id.toString()), + ); + + if (newAssignments.length === 0) { + return tag; // No new assignments to be made + } + + // Assign all the ancestor tags + const allAncestorTags = [tag._id]; + let currentTag = tag; + while (currentTag?.parentTagId) { + const currentParentTag = await OrganizationTagUser.findOne({ + _id: currentTag.parentTagId, + }).lean(); + + if (currentParentTag) { + allAncestorTags.push(currentParentTag?._id); + currentTag = currentParentTag; + } + } + + const tagUserDocs = newAssignments.flatMap((user) => + allAncestorTags.map((tagId) => ({ + updateOne: { + filter: { userId: user._id, tagId }, + update: { + $setOnInsert: { + userId: user._id, + tagId, + organizationId: tag.organizationId, + }, + }, + upsert: true, + setDefaultsOnInsert: true, + }, + })), + ); + + if (tagUserDocs.length > 0) { + await TagUser.bulkWrite(tagUserDocs); + } + + return tag; + }; diff --git a/src/resolvers/Mutation/addUserToGroupChat.ts b/src/resolvers/Mutation/addUserToGroupChat.ts deleted file mode 100644 index d71535f5788..00000000000 --- a/src/resolvers/Mutation/addUserToGroupChat.ts +++ /dev/null @@ -1,127 +0,0 @@ -import "dotenv/config"; -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, - USER_ALREADY_MEMBER_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import { GroupChat, Organization, User } from "../../models"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { adminCheck } from "../../utilities"; -import type { InterfaceGroupChat } from "../../models"; - -/** - * Mutation resolver function to add a user to a group chat. - * - * This function performs the following actions: - * 1. Checks if the group chat specified by `args.chatId` exists. - * 2. Checks if the organization associated with the group chat exists. - * 3. Verifies that the current user (identified by `context.userId`) is an admin of the organization. - * 4. Confirms that the user to be added (specified by `args.userId`) exists. - * 5. Ensures that the user is not already a member of the group chat. - * 6. Adds the user to the list of users in the group chat and returns the updated group chat. - * - * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. - * @param args - The arguments for the mutation, including: - * - `chatId`: The ID of the group chat to which the user will be added. - * - `userId`: The ID of the user to be added to the group chat. - * @param context - The context for the mutation, including: - * - `userId`: The ID of the current user making the request. - * - * @returns A promise that resolves to the updated group chat document with the new user added. - * - * @see GroupChat - The GroupChat model used to interact with the group chats collection in the database. - * @see Organization - The Organization model used to interact with the organizations collection in the database. - * @see User - The User model used to interact with the users collection in the database. - * @see MutationResolvers - The type definition for the mutation resolvers. - * @see adminCheck - Utility function to check if the current user is an admin of the organization. - * @see findOrganizationsInCache - Service function to retrieve organizations from cache. - * @see cacheOrganizations - Service function to cache updated organization data. - */ -export const addUserToGroupChat: MutationResolvers["addUserToGroupChat"] = - async (_parent, args, context) => { - const groupChat = await GroupChat.findOne({ - _id: args.chatId, - }).lean(); - - // Checks whether groupChat exists. - if (!groupChat) { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - let organization; - const organizationFoundInCache = await findOrganizationsInCache([ - groupChat.organization, - ]); - - organization = organizationFoundInCache[0]; - - if (organizationFoundInCache.includes(null)) { - organization = await Organization.findOne({ - _id: groupChat.organization, - }).lean(); - if (organization) { - await cacheOrganizations([organization]); - } - } - - // Checks whether organization exists. - if (!organization) { - throw new errors.NotFoundError( - requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - ORGANIZATION_NOT_FOUND_ERROR.CODE, - ORGANIZATION_NOT_FOUND_ERROR.PARAM, - ); - } - - // Checks whether currentUser with _id === context.userId is an admin of organization. - await adminCheck(context.userId, organization); - - const userExists = !!(await User.exists({ - _id: args.userId, - })); - - // Checks whether user with _id === args.userId exists. - if (userExists === false) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - const isUserGroupChatMember = groupChat.users.some((user) => - user.equals(args.userId), - ); - - // Checks whether user with _id === args.userId is already a member of groupChat. - if (isUserGroupChatMember === true) { - throw new errors.ConflictError( - requestContext.translate(USER_ALREADY_MEMBER_ERROR.MESSAGE), - USER_ALREADY_MEMBER_ERROR.CODE, - USER_ALREADY_MEMBER_ERROR.PARAM, - ); - } - - // Adds args.userId to users list on groupChat's document and returns the updated groupChat. - return (await GroupChat.findOneAndUpdate( - { - _id: args.chatId, - }, - { - $push: { - users: args.userId, - }, - }, - { - new: true, - }, - ).lean()) as InterfaceGroupChat; - }; diff --git a/src/resolvers/Mutation/adminRemoveGroup.ts b/src/resolvers/Mutation/adminRemoveGroup.ts deleted file mode 100644 index 972f6bca066..00000000000 --- a/src/resolvers/Mutation/adminRemoveGroup.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import { AppUserProfile, GroupChat, Organization, User } from "../../models"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { adminCheck } from "../../utilities"; - -/** - * Mutation resolver function to remove a group chat. - * - * This function performs the following actions: - * 1. Checks if the group chat specified by `args.groupId` exists. - * 2. Verifies that the organization associated with the group chat exists. - * 3. Ensures that the current user (identified by `context.userId`) exists. - * 4. Checks that the current user is authorized as an admin of the organization. - * 5. Deletes the group chat from the database. - * - * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. - * @param args - The arguments for the mutation, including: - * - `groupId`: The ID of the group chat to be removed. - * @param context - The context for the mutation, including: - * - `userId`: The ID of the current user making the request. - * - * @returns A promise that resolves to the deleted group chat document. - * - * @see GroupChat - The GroupChat model used to interact with the group chats collection in the database. - * @see Organization - The Organization model used to interact with the organizations collection in the database. - * @see User - The User model used to interact with the users collection in the database. - * @see AppUserProfile - The AppUserProfile model used to retrieve the user's profile information. - * @see MutationResolvers - The type definition for the mutation resolvers. - * @see adminCheck - Utility function to check if the current user is an admin of the organization. - * @see findOrganizationsInCache - Service function to retrieve organizations from cache. - * @see cacheOrganizations - Service function to cache updated organization data. - */ -export const adminRemoveGroup: MutationResolvers["adminRemoveGroup"] = async ( - _parent, - args, - context, -) => { - const groupChat = await GroupChat.findOne({ - _id: args.groupId, - }).lean(); - - // Checks whether groupChat exists. - if (!groupChat) { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - let organization; - - const organizationFoundInCache = await findOrganizationsInCache([ - groupChat.organization, - ]); - - organization = organizationFoundInCache[0]; - - if (organizationFoundInCache.includes(null)) { - organization = await Organization.findOne({ - _id: groupChat.organization, - }).lean(); - if (organization) { - await cacheOrganizations([organization]); - } - } - - // Checks whether organization exists. - if (!organization) { - throw new errors.NotFoundError( - requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - ORGANIZATION_NOT_FOUND_ERROR.CODE, - ORGANIZATION_NOT_FOUND_ERROR.PARAM, - ); - } - - const currentUserExists = !!(await User.exists({ - _id: context.userId, - })); - - // Checks currentUser with _id === context.userId exists. - if (currentUserExists === false) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - const currentUserAppProfile = await AppUserProfile.findOne({ - userId: context.userId, - }).lean(); - if (!currentUserAppProfile) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - // Checks whether currentUser with _id === context.userId is an admin of organization. - await adminCheck(context.userId, organization); - - //remove message from organization - // org.overwrite({ - // ...org._doc, - // messages: org._doc.posts.filter((message) => message != args.messageId), - // }); - // await org.save(); - - // //remove post from user - // user.overwrite({ - // ...user._doc, - // messages: user._doc.posts.filter((message) => message != args.messageId), - // }); - // await user.save(); - - // Deletes the groupChat. - await GroupChat.deleteOne({ - _id: groupChat._id, - }); - - // Returns the deleted groupChat. - return groupChat; -}; diff --git a/src/resolvers/Mutation/assignToUserTags.ts b/src/resolvers/Mutation/assignToUserTags.ts new file mode 100644 index 00000000000..0aa6ad0ce05 --- /dev/null +++ b/src/resolvers/Mutation/assignToUserTags.ts @@ -0,0 +1,174 @@ +import { Types } from "mongoose"; +import { + TAG_NOT_FOUND, + USER_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, +} from "../../constants"; +import { errors, requestContext } from "../../libraries"; +import type { + InterfaceAppUserProfile, + InterfaceOrganizationTagUser, + InterfaceUser, +} from "../../models"; +import { + AppUserProfile, + OrganizationTagUser, + TagUser, + User, +} from "../../models"; +import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; +import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; +import { cacheUsers } from "../../services/UserCache/cacheUser"; +import { findUserInCache } from "../../services/UserCache/findUserInCache"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + +/** + * This function enables an admin to assign multiple tags to users with a specified tag. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire application + * @remarks The following checks are done: + * 1. If the current user exists and has a profile. + * 2. If the current user is an admin for the organization of the tags. + * 3. If the currentTagId exists and the selected tags exist. + * 4. Assign the tags to users who have the currentTagId. + * @returns Array of tags that were assigned to users. + */ +export const assignToUserTags: MutationResolvers["assignToUserTags"] = async ( + _parent, + args, + context, +) => { + let currentUser: InterfaceUser | null; + const userFoundInCache = await findUserInCache([context.userId]); + currentUser = userFoundInCache[0]; + if (currentUser === null) { + currentUser = await User.findOne({ + _id: context.userId, + }).lean(); + if (currentUser !== null) { + await cacheUsers([currentUser]); + } + } + + // Checks whether the currentUser exists. + if (!currentUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + let currentUserAppProfile: InterfaceAppUserProfile | null; + + const appUserProfileFoundInCache = await findAppUserProfileCache([ + currentUser.appUserProfileId.toString(), + ]); + + currentUserAppProfile = appUserProfileFoundInCache[0]; + if (currentUserAppProfile === null) { + currentUserAppProfile = await AppUserProfile.findOne({ + userId: currentUser._id, + }).lean(); + if (currentUserAppProfile !== null) { + await cacheAppUserProfile([currentUserAppProfile]); + } + } + + if (!currentUserAppProfile) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Get the current tag object + const currentTag = await OrganizationTagUser.findOne({ + _id: args.input.currentTagId, + }).lean(); + + if (!currentTag) { + throw new errors.NotFoundError( + requestContext.translate(TAG_NOT_FOUND.MESSAGE), + TAG_NOT_FOUND.CODE, + TAG_NOT_FOUND.PARAM, + ); + } + + // Boolean to determine whether user is an admin of the organization of the current tag. + const currentUserIsOrganizationAdmin = currentUserAppProfile.adminFor.some( + (orgId) => orgId?.toString() === currentTag.organizationId.toString(), + ); + + if (!(currentUserIsOrganizationAdmin || currentUserAppProfile.isSuperAdmin)) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Find selected tags & all users tagged with the current tag + const [selectedTags, usersWithCurrentTag] = await Promise.all([ + OrganizationTagUser.find({ + _id: { $in: args.input.selectedTagIds }, + }).lean(), + TagUser.find({ tagId: currentTag._id }).lean(), + ]); + + const userIdsWithCurrentTag = usersWithCurrentTag.map( + (userTag) => userTag.userId, + ); + + // Check if all requested tags were found + if (selectedTags.length !== args.input.selectedTagIds.length) { + throw new errors.NotFoundError( + requestContext.translate(TAG_NOT_FOUND.MESSAGE), + TAG_NOT_FOUND.CODE, + TAG_NOT_FOUND.PARAM, + ); + } + + // Find and assign ancestor tags + const allTagsToAssign = new Set(); + for (const tag of selectedTags) { + let currentTagToProcess: InterfaceOrganizationTagUser | null = tag; + while (currentTagToProcess) { + allTagsToAssign.add(currentTagToProcess._id.toString()); + if (currentTagToProcess.parentTagId) { + const parentTag: InterfaceOrganizationTagUser | null = + await OrganizationTagUser.findOne({ + _id: currentTagToProcess.parentTagId, + }).lean(); + currentTagToProcess = parentTag || null; + } else { + currentTagToProcess = null; + } + } + } + + const tagUserDocs = userIdsWithCurrentTag.flatMap((userId) => + Array.from(allTagsToAssign).map((tagId) => ({ + updateOne: { + filter: { userId, tagId: new Types.ObjectId(tagId) }, + update: { + $setOnInsert: { + userId, + tagId: new Types.ObjectId(tagId), + organizationId: currentTag.organizationId, + }, + }, + upsert: true, + setDefaultsOnInsert: true, + }, + })), + ); + + if (tagUserDocs.length > 0) { + await TagUser.bulkWrite(tagUserDocs); + } + + return currentTag; +}; diff --git a/src/resolvers/Mutation/assignUserTag.ts b/src/resolvers/Mutation/assignUserTag.ts index ee2186642dc..d7f076ae8a5 100644 --- a/src/resolvers/Mutation/assignUserTag.ts +++ b/src/resolvers/Mutation/assignUserTag.ts @@ -171,7 +171,13 @@ export const assignUserTag: MutationResolvers["assignUserTag"] = async ( const tagUserDocs = allAncestorTags.map((tagId) => ({ updateOne: { filter: { userId: assigneeId, tagId }, - update: { $setOnInsert: { userId: assigneeId, tagId } }, + update: { + $setOnInsert: { + userId: assigneeId, + tagId, + organizationId: tag.organizationId, + }, + }, upsert: true, setDefaultsOnInsert: true, }, diff --git a/src/resolvers/Mutation/createActionItem.ts b/src/resolvers/Mutation/createActionItem.ts index c38357a2828..718bf1caba6 100644 --- a/src/resolvers/Mutation/createActionItem.ts +++ b/src/resolvers/Mutation/createActionItem.ts @@ -3,31 +3,31 @@ import { ACTION_ITEM_CATEGORY_IS_DISABLED, ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR, EVENT_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, - USER_NOT_MEMBER_FOR_ORGANIZATION, } from "../../constants"; import { errors, requestContext } from "../../libraries"; import type { InterfaceActionItem, - InterfaceAppUserProfile, InterfaceEvent, - InterfaceUser, + InterfaceEventVolunteer, + InterfaceEventVolunteerGroup, } from "../../models"; import { ActionItem, ActionItemCategory, - AppUserProfile, Event, - User, + EventVolunteer, + EventVolunteerGroup, } from "../../models"; -import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; -import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; import { cacheEvents } from "../../services/EventCache/cacheEvents"; import { findEventsInCache } from "../../services/EventCache/findEventInCache"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { + checkAppUserProfileExists, + checkUserExists, +} from "../../utilities/checks"; /** * Creates a new action item and assigns it to a user. @@ -38,18 +38,18 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; * 2. Ensures that the current user has an associated app user profile. * 3. Checks if the assignee exists. * 4. Validates if the action item category exists and is not disabled. - * 5. Confirms that the assignee is a member of the organization associated with the action item category. - * 6. If the action item is related to an event, checks if the event exists and whether the current user is an admin of that event. - * 7. Verifies if the current user is an admin of the organization or a superadmin. + * 5. If the action item is related to an event, checks if the event exists and whether the current user is an admin of that event. + * 6. Verifies if the current user is an admin of the organization or a superadmin. * * @param _parent - The parent object for the mutation (not used in this function). * @param args - The arguments provided with the request, including: * - `data`: An object containing: * - `assigneeId`: The ID of the user to whom the action item is assigned. + * - `assigneeType`: The type of the assignee (EventVolunteer or EventVolunteerGroup). * - `preCompletionNotes`: Notes to be added before the action item is completed. * - `dueDate`: The due date for the action item. * - `eventId` (optional): The ID of the event associated with the action item. - * - `actionItemCategoryId`: The ID of the action item category. + * - `actionItemCategoryId`: The ID of the action item category. * @param context - The context of the entire application, including user information and other context-specific data. * * @returns A promise that resolves to the created action item object. @@ -60,62 +60,41 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( args, context, ): Promise => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); + const currentUser = await checkUserExists(context.userId); + const currentUserAppProfile = await checkAppUserProfileExists(currentUser); + + const { + assigneeId, + assigneeType, + preCompletionNotes, + allottedHours, + dueDate, + eventId, + } = args.data; + + let assignee: InterfaceEventVolunteer | InterfaceEventVolunteerGroup | null; + if (assigneeType === "EventVolunteer") { + assignee = await EventVolunteer.findById(assigneeId) + .populate("user") + .lean(); + if (!assignee) { + throw new errors.NotFoundError( + requestContext.translate(EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE), + EVENT_VOLUNTEER_NOT_FOUND_ERROR.CODE, + EVENT_VOLUNTEER_NOT_FOUND_ERROR.PARAM, + ); } - } - - // Checks whether currentUser with _id === context.userId exists. - if (currentUser === null) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - let currentUserAppProfile: InterfaceAppUserProfile | null; - const appUserProfileFoundInCache = await findAppUserProfileCache([ - currentUser.appUserProfileId?.toString(), - ]); - currentUserAppProfile = appUserProfileFoundInCache[0]; - if (currentUserAppProfile === null) { - currentUserAppProfile = await AppUserProfile.findOne({ - userId: currentUser._id, - }).lean(); - if (currentUserAppProfile !== null) { - await cacheAppUserProfile([currentUserAppProfile]); + } else if (assigneeType === "EventVolunteerGroup") { + assignee = await EventVolunteerGroup.findById(assigneeId).lean(); + if (!assignee) { + throw new errors.NotFoundError( + requestContext.translate(EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE), + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.CODE, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.PARAM, + ); } } - if (!currentUserAppProfile) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - const assignee = await User.findOne({ - _id: args.data.assigneeId, - }); - - // Checks whether the assignee exists. - if (assignee === null) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - const actionItemCategory = await ActionItemCategory.findOne({ _id: args.actionItemCategoryId, }).lean(); @@ -138,36 +117,17 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( ); } - let asigneeIsOrganizationMember = false; - asigneeIsOrganizationMember = assignee.joinedOrganizations.some( - (organizationId) => - organizationId === actionItemCategory.organizationId || - new mongoose.Types.ObjectId(organizationId.toString()).equals( - actionItemCategory.organizationId, - ), - ); - - // Checks if the asignee is a member of the organization - if (!asigneeIsOrganizationMember) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE), - USER_NOT_MEMBER_FOR_ORGANIZATION.CODE, - USER_NOT_MEMBER_FOR_ORGANIZATION.PARAM, - ); - } - let currentUserIsEventAdmin = false; - - if (args.data.eventId) { + if (eventId) { let currEvent: InterfaceEvent | null; - const eventFoundInCache = await findEventsInCache([args.data.eventId]); + const eventFoundInCache = await findEventsInCache([eventId]); currEvent = eventFoundInCache[0]; if (eventFoundInCache[0] === null) { currEvent = await Event.findOne({ - _id: args.data.eventId, + _id: eventId, }).lean(); if (currEvent !== null) { @@ -203,6 +163,7 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( ); // Checks whether the currentUser is authorized for the operation. + /* c8 ignore start */ if ( currentUserIsEventAdmin === false && currentUserIsOrgAdmin === false && @@ -214,19 +175,41 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( USER_NOT_AUTHORIZED_ERROR.PARAM, ); } + /* c8 ignore stop */ // Creates and returns the new action item. const createActionItem = await ActionItem.create({ - assignee: args.data.assigneeId, + assignee: assigneeType === "EventVolunteer" ? assigneeId : undefined, + assigneeGroup: + assigneeType === "EventVolunteerGroup" ? assigneeId : undefined, + assigneeUser: assigneeType === "User" ? assigneeId : undefined, + assigneeType, assigner: context.userId, actionItemCategory: args.actionItemCategoryId, - preCompletionNotes: args.data.preCompletionNotes, - allotedHours: args.data.allotedHours, - dueDate: args.data.dueDate, - event: args.data.eventId, + preCompletionNotes, + allottedHours, + dueDate, + event: eventId, organization: actionItemCategory.organizationId, creator: context.userId, }); + if (assigneeType === "EventVolunteer") { + await EventVolunteer.findByIdAndUpdate(assigneeId, { + $addToSet: { assignments: createActionItem._id }, + }); + } else if (assigneeType === "EventVolunteerGroup") { + const newGrp = (await EventVolunteerGroup.findByIdAndUpdate( + assigneeId, + { $addToSet: { assignments: createActionItem._id } }, + { new: true }, + ).lean()) as InterfaceEventVolunteerGroup; + + await EventVolunteer.updateMany( + { _id: { $in: newGrp.volunteers } }, + { $addToSet: { assignments: createActionItem._id } }, + ); + } + return createActionItem.toObject(); }; diff --git a/src/resolvers/Mutation/createChat.ts b/src/resolvers/Mutation/createChat.ts new file mode 100644 index 00000000000..650707cb444 --- /dev/null +++ b/src/resolvers/Mutation/createChat.ts @@ -0,0 +1,80 @@ +import { + ORGANIZATION_NOT_FOUND_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../constants"; +import { errors, requestContext } from "../../libraries"; +import { Chat, Organization, User } from "../../models"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * This function enables to create a chat. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire application + * @remarks The following checks are done: + * 1. If the user exists + * 2. If the organization exists + * @returns Created chat + */ +export const createChat: MutationResolvers["createChat"] = async ( + _parent, + args, + context, +) => { + let organization; + if (args.data.isGroup && args.data.organizationId) { + organization = await Organization.findOne({ + _id: args.data.organizationId, + }); + + // Checks whether organization with _id === args.data.organizationId exists. + if (!organization) { + throw new errors.NotFoundError( + requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), + ORGANIZATION_NOT_FOUND_ERROR.CODE, + ORGANIZATION_NOT_FOUND_ERROR.PARAM, + ); + } + } + + // const userExists = (await User.exists({ + // _id: { $in: args.data.userIds }, + // })) as unknown as string[]; + + const userExists = await User.find({ + _id: { $in: args.data.userIds }, + }).lean(); + + if (userExists && userExists.length !== args.data.userIds.length) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + const now = new Date(); + + const chatPayload = args.data.isGroup + ? { + isGroup: args.data.isGroup, + creatorId: context.userId, + users: args.data.userIds, + organization: args.data.organizationId, + name: args.data?.name, + admins: [context.userId], + createdAt: now, + updatedAt: now, + image: args.data.image, + } + : { + creatorId: context.userId, + users: args.data.userIds, + isGroup: args.data.isGroup, + createdAt: now, + updatedAt: now, + }; + + const createdChat = await Chat.create(chatPayload); + + return createdChat; +}; diff --git a/src/resolvers/Mutation/createDirectChat.ts b/src/resolvers/Mutation/createDirectChat.ts deleted file mode 100644 index 85d352638af..00000000000 --- a/src/resolvers/Mutation/createDirectChat.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { User, DirectChat } from "../../models"; -import { errors, requestContext } from "../../libraries"; - -import { USER_NOT_FOUND_ERROR } from "../../constants"; - -/** - * Creates a new direct chat and associates it with an organization. - * - * This resolver performs the following steps: - * - * 1. Retrieves the organization based on the provided `organizationId`. - * 2. Checks if the organization exists, either from cache or database. - * 3. Validates that all user IDs provided in `userIds` exist. - * 4. Creates a new direct chat with the specified users and organization. - * - * @param _parent - The parent object, not used in this resolver. - * @param args - The input arguments for the mutation, including: - * - `data`: An object containing: - * - `organizationId`: The ID of the organization to associate with the direct chat. - * - `userIds`: An array of user IDs to be included in the direct chat. - * @param context - The context object containing user information (context.userId). - * - * @returns A promise that resolves to the created direct chat object. - * - * @remarks This function includes caching operations to optimize data retrieval and ensures that all user IDs are valid before creating the direct chat. - */ -export const createDirectChat: MutationResolvers["createDirectChat"] = async ( - _parent, - args, - context, -) => { - // Variable to store list of users to be members of directChat. - const usersInDirectChat = []; - - // Loops over each item in args.data.userIds list. - for await (const userId of args.data.userIds) { - const userExists = !!(await User.exists({ - _id: userId, - })); - - // Checks whether user with _id === userId exists. - if (userExists === false) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - usersInDirectChat.push(userId); - } - - // Creates new directChat. - const createdDirectChat = await DirectChat.create({ - creatorId: context.userId, - users: usersInDirectChat, - }); - - // Returns createdDirectChat. - return createdDirectChat.toObject(); -}; diff --git a/src/resolvers/Mutation/createEventVolunteer.ts b/src/resolvers/Mutation/createEventVolunteer.ts index decaa931fcf..987c88dfb27 100644 --- a/src/resolvers/Mutation/createEventVolunteer.ts +++ b/src/resolvers/Mutation/createEventVolunteer.ts @@ -1,29 +1,25 @@ import { EVENT_NOT_FOUND_ERROR, - EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, EVENT_VOLUNTEER_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, } from "../../constants"; import { errors, requestContext } from "../../libraries"; -import type { InterfaceUser } from "../../models"; -import { Event, EventVolunteerGroup, User } from "../../models"; +import { Event, User, VolunteerMembership } from "../../models"; import { EventVolunteer } from "../../models/EventVolunteer"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { adminCheck } from "../../utilities"; +import { checkUserExists } from "../../utilities/checks"; /** * Creates a new event volunteer entry. * * This function performs the following actions: - * 1. Verifies the existence of the current user. - * 2. Verifies the existence of the volunteer user. - * 3. Verifies the existence of the event. - * 4. Verifies the existence of the volunteer group. - * 5. Ensures that the current user is the leader of the volunteer group. - * 6. Creates a new event volunteer record. - * 7. Adds the newly created volunteer to the group's list of volunteers. + * 1. Validates the existence of the current user. + * 2. Checks if the specified user and event exist. + * 3. Verifies that the current user is an admin of the event. + * 4. Creates a new volunteer entry for the event. + * 5. Creates a volunteer membership record for the new volunteer. + * 6. Returns the created event volunteer record. * * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. * @param args - The arguments for the mutation, including: @@ -38,25 +34,11 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; */ export const createEventVolunteer: MutationResolvers["createEventVolunteer"] = async (_parent, args, context) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - if (!currentUser) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - const volunteerUser = await User.findOne({ _id: args.data?.userId }).lean(); + const { eventId, userId } = args.data; + const currentUser = await checkUserExists(context.userId); + + // Check if the volunteer user exists + const volunteerUser = await User.findById(userId).lean(); if (!volunteerUser) { throw new errors.NotFoundError( requestContext.translate(EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE), @@ -64,7 +46,8 @@ export const createEventVolunteer: MutationResolvers["createEventVolunteer"] = EVENT_VOLUNTEER_NOT_FOUND_ERROR.PARAM, ); } - const event = await Event.findById(args.data.eventId); + // Check if the event exists + const event = await Event.findById(eventId).populate("organization").lean(); if (!event) { throw new errors.NotFoundError( requestContext.translate(EVENT_NOT_FOUND_ERROR.MESSAGE), @@ -72,18 +55,18 @@ export const createEventVolunteer: MutationResolvers["createEventVolunteer"] = EVENT_NOT_FOUND_ERROR.PARAM, ); } - const group = await EventVolunteerGroup.findOne({ - _id: args.data.groupId, - }).lean(); - if (!group) { - throw new errors.NotFoundError( - requestContext.translate(EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE), - EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.CODE, - EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.PARAM, - ); - } - if (group.leaderId.toString() !== currentUser._id.toString()) { + const userIsEventAdmin = event.admins.some( + (admin) => admin.toString() === currentUser?._id.toString(), + ); + + // Checks creator of the event or admin of the organization + const isAdmin = await adminCheck( + currentUser._id, + event.organization, + false, + ); + if (!isAdmin && !userIsEventAdmin) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), USER_NOT_AUTHORIZED_ERROR.CODE, @@ -91,24 +74,21 @@ export const createEventVolunteer: MutationResolvers["createEventVolunteer"] = ); } + // create the volunteer const createdVolunteer = await EventVolunteer.create({ - userId: args.data.userId, - eventId: args.data.eventId, - groupId: args.data.groupId, - isAssigned: false, - isInvited: true, - creatorId: context.userId, + user: userId, + event: eventId, + creator: context.userId, + groups: [], + }); + + // create volunteer membership record + await VolunteerMembership.create({ + volunteer: createdVolunteer._id, + event: eventId, + status: "invited", + createdBy: context.userId, }); - await EventVolunteerGroup.findOneAndUpdate( - { - _id: args.data.groupId, - }, - { - $push: { - volunteers: createdVolunteer._id, - }, - }, - ); return createdVolunteer.toObject(); }; diff --git a/src/resolvers/Mutation/createEventVolunteerGroup.ts b/src/resolvers/Mutation/createEventVolunteerGroup.ts index 060a2dde494..d3c26adbd02 100644 --- a/src/resolvers/Mutation/createEventVolunteerGroup.ts +++ b/src/resolvers/Mutation/createEventVolunteerGroup.ts @@ -1,14 +1,17 @@ import { EVENT_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, } from "../../constants"; import { errors, requestContext } from "../../libraries"; -import type { InterfaceUser } from "../../models"; -import { Event, EventVolunteerGroup, User } from "../../models"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; +import { + Event, + EventVolunteer, + EventVolunteerGroup, + VolunteerMembership, +} from "../../models"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { adminCheck } from "../../utilities"; +import { checkUserExists } from "../../utilities/checks"; /** * Creates a new event volunteer group and associates it with an event. @@ -19,14 +22,19 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; * 2. Checks if the specified event exists. * 3. Verifies that the current user is an admin of the event. * 4. Creates a new volunteer group for the event. - * 5. Updates the event to include the newly created volunteer group. + * 5. Fetches or creates new volunteers for the group. + * 6. Creates volunteer group membership records for the new volunteers. + * 7. Updates the event to include the new volunteer group. * * @param _parent - The parent object, not used in this resolver. * @param args - The input arguments for the mutation, including: * - `data`: An object containing: - * - `eventId`: The ID of the event to associate the volunteer group with. - * - `name`: The name of the volunteer group. - * - `volunteersRequired`: The number of volunteers required for the group. + * - `eventId`: The ID of the event to associate the volunteer group with. + * - `name`: The name of the volunteer group. + * - `description`: A description of the volunteer group. + * - `leaderId`: The ID of the user who will lead the volunteer group. + * - `volunteerIds`: An array of user IDs for the volunteers in the group. + * - `volunteersRequired`: The number of volunteers required for the group. * @param context - The context object containing user information (context.userId). * * @returns A promise that resolves to the created event volunteer group object. @@ -35,25 +43,20 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; */ export const createEventVolunteerGroup: MutationResolvers["createEventVolunteerGroup"] = async (_parent, args, context) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - if (!currentUser) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - const event = await Event.findById(args.data.eventId); + const { + eventId, + name, + description, + leaderId, + volunteerUserIds, + volunteersRequired, + } = args.data; + // Validate the existence of the current user + const currentUser = await checkUserExists(context.userId); + + const event = await Event.findById(args.data.eventId) + .populate("organization") + .lean(); if (!event) { throw new errors.NotFoundError( requestContext.translate(EVENT_NOT_FOUND_ERROR.MESSAGE), @@ -66,7 +69,13 @@ export const createEventVolunteerGroup: MutationResolvers["createEventVolunteerG (admin) => admin.toString() === currentUser?._id.toString(), ); - if (!userIsEventAdmin) { + const isAdmin = await adminCheck( + currentUser._id, + event.organization, + false, + ); + // Checks if user is Event Admin or Admin of the organization + if (!isAdmin && !userIsEventAdmin) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), USER_NOT_AUTHORIZED_ERROR.CODE, @@ -74,24 +83,57 @@ export const createEventVolunteerGroup: MutationResolvers["createEventVolunteerG ); } + // Create the new volunteer group const createdVolunteerGroup = await EventVolunteerGroup.create({ - eventId: args.data.eventId, - creatorId: context.userId, - leaderId: context.userId, - name: args.data.name, - volunteersRequired: args.data?.volunteersRequired, + creator: context.userId, + event: eventId, + leader: leaderId, + name, + description, + volunteers: [], + volunteersRequired, }); - await Event.findOneAndUpdate( - { - _id: args.data.eventId, - }, - { - $push: { - volunteerGroups: createdVolunteerGroup._id, - }, - }, + // Fetch Volunteers or Create New Ones if Necessary + const volunteers = await EventVolunteer.find({ + user: { $in: volunteerUserIds }, + event: eventId, + }).lean(); + + const existingVolunteerIds = volunteers.map((vol) => vol.user.toString()); + const newVolunteerUserIds = volunteerUserIds.filter( + (id) => !existingVolunteerIds.includes(id), ); + // Bulk Create New Volunteers if Needed + const newVolunteers = await EventVolunteer.insertMany( + newVolunteerUserIds.map((userId) => ({ + user: userId, + event: eventId, + creator: context.userId, + groups: [], + })), + ); + + const allVolunteerIds = [ + ...volunteers.map((v) => v._id.toString()), + ...newVolunteers.map((v) => v._id.toString()), + ]; + + // Bulk Create VolunteerMembership Records + await VolunteerMembership.insertMany( + allVolunteerIds.map((volunteerId) => ({ + volunteer: volunteerId, + group: createdVolunteerGroup._id, + event: eventId, + status: "invited", + createdBy: context.userId, + })), + ); + + await Event.findByIdAndUpdate(eventId, { + $push: { volunteerGroups: createdVolunteerGroup._id }, + }); + return createdVolunteerGroup.toObject(); }; diff --git a/src/resolvers/Mutation/createGroupChat.ts b/src/resolvers/Mutation/createGroupChat.ts deleted file mode 100644 index 17dc1548b0d..00000000000 --- a/src/resolvers/Mutation/createGroupChat.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import { GroupChat, Organization, User } from "../../models"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Creates a new group chat and associates it with a specified organization. - * - * This resolver performs the following actions: - * - * 1. Checks if the specified organization exists in the cache, and if not, fetches it from the database and caches it. - * 2. Verifies that the organization with the given ID exists. - * 3. Checks if each user specified in the `userIds` list exists. - * 4. Creates a new group chat with the specified users, organization, and title. - * - * @param _parent - The parent object, not used in this resolver. - * @param args - The input arguments for the mutation, including: - * - `data`: An object containing: - * - `organizationId`: The ID of the organization to associate with the group chat. - * - `userIds`: A list of user IDs to be added to the group chat. - * - `title`: The title of the group chat. - * @param context - The context object containing user information (context.userId). - * - * @returns A promise that resolves to the created group chat object. - * - * @remarks This function ensures the existence of the organization and users, and caches the organization if it is not already cached. It returns the created group chat object. - */ -export const createGroupChat: MutationResolvers["createGroupChat"] = async ( - _parent, - args, - context, -) => { - let organization; - - const organizationFoundInCache = await findOrganizationsInCache([ - args.data.organizationId, - ]); - - organization = organizationFoundInCache[0]; - - if (organizationFoundInCache.includes(null)) { - organization = await Organization.findOne({ - _id: args.data.organizationId, - }).lean(); - if (organization) await cacheOrganizations([organization]); - } - - // Checks whether the organization with _id === args.data.organizationId exists. - if (!organization) { - throw new errors.NotFoundError( - requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - ORGANIZATION_NOT_FOUND_ERROR.CODE, - ORGANIZATION_NOT_FOUND_ERROR.PARAM, - ); - } - - // Variable to store list of users to be members of group chat. - const usersInGroupChat = []; - - // Loops over each item in args.data.userIds list. - for await (const userId of args.data.userIds) { - const userExists = !!(await User.exists({ - _id: userId, - })); - - // Checks whether user with _id === userId exists. - if (userExists === false) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - usersInGroupChat.push(userId); - } - - // Creates new group chat. - const createdGroupChat = await GroupChat.create({ - creatorId: context.userId, - users: usersInGroupChat, - organization: args.data?.organizationId, - title: args.data?.title, - }); - - // Returns created group chat. - return createdGroupChat.toObject(); -}; diff --git a/src/resolvers/Mutation/createMessageChat.ts b/src/resolvers/Mutation/createMessageChat.ts deleted file mode 100644 index e7d6fdab4ce..00000000000 --- a/src/resolvers/Mutation/createMessageChat.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import type { InterfaceAppUserProfile, InterfaceUser } from "../../models"; -import { AppUserProfile, MessageChat, User } from "../../models"; -import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; -import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; - -/** - * Creates a new chat message between users. - * - * This function performs the following actions: - * 1. Verifies the existence of the current user. - * 2. Retrieves and caches the current user's details and application profile if not already cached. - * 3. Checks the existence of the receiver user and retrieves their application profile. - * 4. Ensures that both the current user and the receiver have valid application profiles. - * 5. Compares the language codes of the sender and receiver to determine if there is a language barrier. - * 6. Creates a new chat message with the specified content and language barrier status. - * 7. Publishes the created message chat to a pub/sub channel for real-time updates. - * - * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. - * @param args - The arguments for the mutation, including: - * - `data.receiver`: The ID of the user receiving the message. - * - `data.message`: The content of the message being sent. - * @param context - The context for the mutation, including: - * - `userId`: The ID of the current user sending the message. - * - `pubsub`: The pub/sub instance for publishing real-time updates. - * - * @returns The created message chat record. - * - */ -export const createMessageChat: MutationResolvers["createMessageChat"] = async ( - _parent, - args, - context, -) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - if (!currentUser) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - let currentUserAppProfile: InterfaceAppUserProfile | null; - const appUserProfileFoundInCache = await findAppUserProfileCache([ - currentUser.appUserProfileId?.toString(), - ]); - currentUserAppProfile = appUserProfileFoundInCache[0]; - if (currentUserAppProfile === null) { - currentUserAppProfile = await AppUserProfile.findOne({ - userId: currentUser._id, - }).lean(); - if (currentUserAppProfile !== null) { - await cacheAppUserProfile([currentUserAppProfile]); - } - } - - const receiverUser = await User.findOne({ - _id: args.data.receiver, - }).lean(); - - // Checks whether receiverUser exists. - if (!receiverUser) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - const receiverUserAppProfile = await AppUserProfile.findOne({ - userId: receiverUser._id, - }).lean(); - if (!receiverUserAppProfile) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - if (!currentUserAppProfile) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - // Boolean to identify whether both sender and receiver for messageChat have the same appLanguageCode. - const isSenderReceiverLanguageSame = - receiverUserAppProfile?.appLanguageCode === - currentUserAppProfile?.appLanguageCode; - - // Creates new messageChat. - const createdMessageChat = await MessageChat.create({ - sender: currentUser?._id, - receiver: receiverUser._id, - message: args.data.message, - languageBarrier: !isSenderReceiverLanguageSame, - }); - - context.pubsub.publish("CHAT_CHANNEL", { - directMessageChat: { - ...createdMessageChat, - }, - }); - - // Returns createdMessageChat. - return createdMessageChat.toObject(); -}; diff --git a/src/resolvers/Mutation/createVolunteerMembership.ts b/src/resolvers/Mutation/createVolunteerMembership.ts new file mode 100644 index 00000000000..e46407c45ff --- /dev/null +++ b/src/resolvers/Mutation/createVolunteerMembership.ts @@ -0,0 +1,81 @@ +import { EVENT_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR } from "../../constants"; +import { errors, requestContext } from "../../libraries"; +import { Event, User, VolunteerMembership } from "../../models"; +import { EventVolunteer } from "../../models/EventVolunteer"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { checkUserExists } from "../../utilities/checks"; + +/** + * Creates a new event volunteer membership entry. + * + * This function performs the following actions: + * 1. Validates the existence of the current user. + * 2. Checks if the specified user and event exist. + * 3. Creates a new volunteer entry for the event. + * 4. Creates a volunteer membership record for the new volunteer. + * 5. Returns the created vvolunteer membership record. + * + * @param _parent - The parent object for the mutation. This parameter is not used in this resolver. + * @param args - The arguments for the mutation, including: + * - `data.userId`: The ID of the user to be assigned as a volunteer. + * - `data.event`: The ID of the event for which the volunteer is being created. + * - `data.group`: The ID of the volunteer group to which the user is being added. + * - `data.status`: The status of the volunteer membership. + * + * @param context - The context for the mutation, including: + * - `userId`: The ID of the current user performing the operation. + * + * @returns The created event volunteer record. + * + */ +export const createVolunteerMembership: MutationResolvers["createVolunteerMembership"] = + async (_parent, args, context) => { + const { event: eventId, status, group, userId } = args.data; + await checkUserExists(context.userId); + + // Check if the volunteer user exists + const volunteerUser = await User.findById(userId).lean(); + if (!volunteerUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + // Check if the event exists + const event = await Event.findById(eventId).populate("organization").lean(); + if (!event) { + throw new errors.NotFoundError( + requestContext.translate(EVENT_NOT_FOUND_ERROR.MESSAGE), + EVENT_NOT_FOUND_ERROR.CODE, + EVENT_NOT_FOUND_ERROR.PARAM, + ); + } + + // check if event volunteer exists + let eventVolunteer = await EventVolunteer.findOne({ + user: userId, + event: eventId, + }).lean(); + + if (!eventVolunteer) { + // create the volunteer + eventVolunteer = await EventVolunteer.create({ + user: userId, + event: eventId, + creator: context.userId, + groups: [], + }); + } + + // create volunteer membership record + const membership = await VolunteerMembership.create({ + volunteer: eventVolunteer._id, + event: eventId, + status: status, + ...(group && { group }), + createdBy: context.userId, + }); + + return membership.toObject(); + }; diff --git a/src/resolvers/Mutation/index.ts b/src/resolvers/Mutation/index.ts index 390dea99dd7..a02bbf2c920 100644 --- a/src/resolvers/Mutation/index.ts +++ b/src/resolvers/Mutation/index.ts @@ -7,10 +7,10 @@ import { addOrganizationCustomField } from "./addOrganizationCustomField"; import { addOrganizationImage } from "./addOrganizationImage"; import { addUserCustomData } from "./addUserCustomData"; import { addUserImage } from "./addUserImage"; -import { addUserToGroupChat } from "./addUserToGroupChat"; import { addUserToUserFamily } from "./addUserToUserFamily"; -import { adminRemoveGroup } from "./adminRemoveGroup"; +import { addPeopleToUserTag } from "./addPeopleToUserTag"; import { assignUserTag } from "./assignUserTag"; +import { assignToUserTags } from "./assignToUserTags"; import { blockPluginCreationBySuperadmin } from "./blockPluginCreationBySuperadmin"; import { blockUser } from "./blockUser"; import { cancelMembershipRequest } from "./cancelMembershipRequest"; @@ -24,7 +24,7 @@ import { createAgendaCategory } from "./createAgendaCategory"; import { createAgendaItem } from "./createAgendaItem"; import { createAgendaSection } from "./createAgendaSection"; import { createComment } from "./createComment"; -import { createDirectChat } from "./createDirectChat"; +import { createChat } from "./createChat"; import { createDonation } from "./createDonation"; import { createEvent } from "./createEvent"; import { createEventVolunteer } from "./createEventVolunteer"; @@ -32,9 +32,7 @@ import { createFund } from "./createFund"; import { createFundraisingCampaign } from "./createFundraisingCampaign"; import { createEventVolunteerGroup } from "./createEventVolunteerGroup"; import { createFundraisingCampaignPledge } from "./createFundraisingCampaignPledge"; -import { createGroupChat } from "./createGroupChat"; import { createMember } from "./createMember"; -import { createMessageChat } from "./createMessageChat"; import { createOrganization } from "./createOrganization"; import { createPlugin } from "./createPlugin"; import { createPost } from "./createPost"; @@ -42,6 +40,7 @@ import { createSampleOrganization } from "./createSampleOrganization"; import { createUserFamily } from "./createUserFamily"; import { createUserTag } from "./createUserTag"; import { createVenue } from "./createVenue"; +import { createVolunteerMembership } from "./createVolunteerMembership"; import { deleteAdvertisement } from "./deleteAdvertisement"; import { deleteAgendaCategory } from "./deleteAgendaCategory"; import { deleteDonationById } from "./deleteDonationById"; @@ -65,13 +64,11 @@ import { removeAdmin } from "./removeAdmin"; import { removeAgendaItem } from "./removeAgendaItem"; import removeAgendaSection from "./removeAgendaSection"; import { removeComment } from "./removeComment"; -import { removeDirectChat } from "./removeDirectChat"; import { removeEvent } from "./removeEvent"; import { removeEventAttendee } from "./removeEventAttendee"; import { removeEventVolunteer } from "./removeEventVolunteer"; import { removeEventVolunteerGroup } from "./removeEventVolunteerGroup"; import { removeFundraisingCampaignPledge } from "./removeFundraisingCampaingPledge"; -import { removeGroupChat } from "./removeGroupChat"; import { removeMember } from "./removeMember"; import { removeOrganization } from "./removeOrganization"; import { removeOrganizationCustomField } from "./removeOrganizationCustomField"; @@ -80,16 +77,15 @@ import { removePost } from "./removePost"; import { removeSampleOrganization } from "./removeSampleOrganization"; import { removeUserCustomData } from "./removeUserCustomData"; import { removeUserFamily } from "./removeUserFamily"; -import { removeUserFromGroupChat } from "./removeUserFromGroupChat"; import { removeUserFromUserFamily } from "./removeUserFromUserFamily"; import { removeUserImage } from "./removeUserImage"; import { removeUserTag } from "./removeUserTag"; +import { removeFromUserTags } from "./removeFromUserTags"; import { resetCommunity } from "./resetCommunity"; import { revokeRefreshTokenForUser } from "./revokeRefreshTokenForUser"; import { saveFcmToken } from "./saveFcmToken"; import { sendMembershipRequest } from "./sendMembershipRequest"; -import { sendMessageToDirectChat } from "./sendMessageToDirectChat"; -import { sendMessageToGroupChat } from "./sendMessageToGroupChat"; +import { sendMessageToChat } from "./sendMessageToChat"; import { signUp } from "./signUp"; import { togglePostPin } from "./togglePostPin"; import { unassignUserTag } from "./unassignUserTag"; @@ -113,11 +109,12 @@ import { updateFundraisingCampaign } from "./updateFundraisingCampaign"; import { updateLanguage } from "./updateLanguage"; import { updateOrganization } from "./updateOrganization"; import { updatePluginStatus } from "./updatePluginStatus"; -import { updatePost } from "./updatePost"; +import { updateSessionTimeout } from "./updateSessionTimeout"; import { updateUserPassword } from "./updateUserPassword"; import { updateUserProfile } from "./updateUserProfile"; import { updateUserRoleInOrganization } from "./updateUserRoleInOrganization"; import { updateUserTag } from "./updateUserTag"; +import { updateVolunteerMembership } from "./updateVolunteerMembership"; import { createNote } from "./createNote"; import { deleteNote } from "./deleteNote"; import { updateNote } from "./updateNote"; @@ -131,13 +128,13 @@ export const Mutation: MutationResolvers = { addOrganizationImage, addUserCustomData, addUserImage, - addUserToGroupChat, - adminRemoveGroup, addUserToUserFamily, + addPeopleToUserTag, + assignUserTag, + assignToUserTags, removeUserFamily, removeUserFromUserFamily, createUserFamily, - assignUserTag, blockPluginCreationBySuperadmin, blockUser, cancelMembershipRequest, @@ -152,13 +149,11 @@ export const Mutation: MutationResolvers = { createAgendaCategory, createAgendaItem, createAgendaSection, - createDirectChat, + createChat, createDonation, createEvent, createFund, createFundraisingCampaign, - createGroupChat, - createMessageChat, createOrganization, createNote, createPlugin, @@ -167,6 +162,7 @@ export const Mutation: MutationResolvers = { createActionItemCategory, createUserTag, createVenue, + createVolunteerMembership, deleteDonationById, deleteAdvertisement, deleteVenue, @@ -193,12 +189,10 @@ export const Mutation: MutationResolvers = { removeAgendaItem, removeAgendaSection, removeComment, - removeDirectChat, removeEvent, removeEventAttendee, removeEventVolunteer, removeEventVolunteerGroup, - removeGroupChat, removeMember, removeOrganization, removeOrganizationCustomField, @@ -206,15 +200,14 @@ export const Mutation: MutationResolvers = { removeSampleOrganization, removePost, removeUserCustomData, - removeUserFromGroupChat, removeUserImage, removeUserTag, + removeFromUserTags, resetCommunity, revokeRefreshTokenForUser, saveFcmToken, sendMembershipRequest, - sendMessageToDirectChat, - sendMessageToGroupChat, + sendMessageToChat, signUp, togglePostPin, unassignUserTag, @@ -236,10 +229,11 @@ export const Mutation: MutationResolvers = { updateLanguage, updateOrganization, updatePluginStatus, + updateSessionTimeout, updateUserProfile, updateUserPassword, updateUserTag, - updatePost, + updateVolunteerMembership, updateAdvertisement, updateFundraisingCampaign, updateFundraisingCampaignPledge, diff --git a/src/resolvers/Mutation/login.ts b/src/resolvers/Mutation/login.ts index cb825c961dc..072ead8b62e 100644 --- a/src/resolvers/Mutation/login.ts +++ b/src/resolvers/Mutation/login.ts @@ -14,7 +14,7 @@ import { createRefreshToken, } from "../../utilities"; /** - * This function enables login. + * This function enables login. (note: only works when using the last resort SuperAdmin credentials) * @param _parent - parent of current request * @param args - payload provided with the request * @remarks The following checks are done: @@ -55,29 +55,43 @@ export const login: MutationResolvers["login"] = async (_parent, args) => { } let appUserProfile: InterfaceAppUserProfile | null = await AppUserProfile.findOne({ - userId: user._id.toString(), + userId: user._id, appLanguageCode: "en", tokenVersion: 0, }).lean(); if (!appUserProfile) { appUserProfile = await AppUserProfile.create({ - userId: user._id.toString(), + userId: user._id, appLanguageCode: "en", tokenVersion: 0, isSuperAdmin: false, }); - await User.updateOne( + + await User.findOneAndUpdate( { - _id: user._id.toString(), + _id: user._id, }, { - appUserProfileId: appUserProfile?._id?.toString(), + appUserProfileId: appUserProfile?._id, }, + { new: true, lean: true }, ); + + // user = await User.findOne({ + // email: args.data.email.toLowerCase(), + // }).lean(); + + // if (!user) { + // throw new errors.NotFoundError( + // requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + // USER_NOT_FOUND_ERROR.CODE, + // USER_NOT_FOUND_ERROR.PARAM, + // ); + // } } - const accessToken = createAccessToken( + const accessToken = await createAccessToken( user, appUserProfile as InterfaceAppUserProfile, ); @@ -104,7 +118,7 @@ export const login: MutationResolvers["login"] = async (_parent, args) => { // ); await AppUserProfile.findOneAndUpdate( { - user: user._id, + _id: user.appUserProfileId, }, { isSuperAdmin: true, diff --git a/src/resolvers/Mutation/removeDirectChat.ts b/src/resolvers/Mutation/removeDirectChat.ts deleted file mode 100644 index eb1abbe8912..00000000000 --- a/src/resolvers/Mutation/removeDirectChat.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { DirectChat, DirectChatMessage, Organization } from "../../models"; -import { adminCheck } from "../../utilities"; -import { errors, requestContext } from "../../libraries"; -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, -} from "../../constants"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; -/** - * This function enables to remove direct chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the organization exists - * 2. If the chat exists - * 3. If the user is an admin of the organization. - * @returns Deleted chat. - */ -export const removeDirectChat: MutationResolvers["removeDirectChat"] = async ( - _parent, - args, - context, -) => { - let organization; - - const organizationFoundInCache = await findOrganizationsInCache([ - args.organizationId, - ]); - - organization = organizationFoundInCache[0]; - - if (organizationFoundInCache.includes(null)) { - organization = await Organization.findOne({ - _id: args.organizationId, - }).lean(); - if (organization) await cacheOrganizations([organization]); - } - - // Checks whether organization exists. - if (!organization) { - throw new errors.NotFoundError( - requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - ORGANIZATION_NOT_FOUND_ERROR.CODE, - ORGANIZATION_NOT_FOUND_ERROR.PARAM, - ); - } - - const directChat = await DirectChat.findOne({ - _id: args.chatId, - }).lean(); - - // Checks whether directChat exists. - if (!directChat) { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - // Checks whether currentUser with _id === context.userId is an admin of organzation. - await adminCheck(context.userId, organization); - - // Deletes all directChatMessages with _id as one of the ids in directChat.messages list. - await DirectChatMessage.deleteMany({ - _id: { - $in: directChat.messages, - }, - }); - - // Deletes the directChat. - await DirectChat.deleteOne({ - _id: args.chatId, - }); - - // Returns deleted directChat. - return directChat; -}; diff --git a/src/resolvers/Mutation/removeEventVolunteer.ts b/src/resolvers/Mutation/removeEventVolunteer.ts index d0b42ebe9e6..f375b1ae6a6 100644 --- a/src/resolvers/Mutation/removeEventVolunteer.ts +++ b/src/resolvers/Mutation/removeEventVolunteer.ts @@ -1,14 +1,13 @@ import { - EVENT_VOLUNTEER_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import type { InterfaceUser } from "../../models"; -import { EventVolunteer, EventVolunteerGroup, User } from "../../models"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; + EventVolunteer, + EventVolunteerGroup, + VolunteerMembership, +} from "../../models"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { + checkEventVolunteerExists, + checkUserExists, +} from "../../utilities/checks"; /** * This function enables to remove an Event Volunteer. @@ -16,73 +15,33 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; * @param args - payload provided with the request * @param context - context of entire application * @remarks The following checks are done: - * 1. If the current user exists - * 2. If the Event volunteer to be removed exists. - * 3. If the current user is leader of the corresponding event volunteer group. + * 1. If the user exists. + * 2. If the Event Volunteer exists. + * 3. Remove the Event Volunteer from their groups and delete the volunteer. + * 4. Delete the volunteer and their memberships in a single operation. * @returns Event Volunteer. */ export const removeEventVolunteer: MutationResolvers["removeEventVolunteer"] = async (_parent, args, context) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - - if (!currentUser) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } + await checkUserExists(context.userId); + const volunteer = await checkEventVolunteerExists(args.id); - const volunteer = await EventVolunteer.findOne({ - _id: args.id, - }); + // Remove volunteer from their groups and delete the volunteer + const groupIds = volunteer.groups; - if (!volunteer) { - throw new errors.NotFoundError( - requestContext.translate(EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE), - EVENT_VOLUNTEER_NOT_FOUND_ERROR.CODE, - EVENT_VOLUNTEER_NOT_FOUND_ERROR.PARAM, + if (groupIds.length > 0) { + await EventVolunteerGroup.updateMany( + { _id: { $in: groupIds } }, + { $pull: { volunteers: volunteer._id } }, ); } - const group = await EventVolunteerGroup.findById(volunteer.groupId); - - const userIsLeader = - group?.leaderId.toString() === currentUser._id.toString(); - - if (!userIsLeader) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - await EventVolunteer.deleteOne({ - _id: args.id, - }); - - await EventVolunteerGroup.updateOne( - { - _id: volunteer.groupId, - }, - { - $pull: { - volunteers: volunteer._id, - }, - }, - ); + // Delete the volunteer and their memberships in a single operation + await Promise.all([ + EventVolunteer.deleteOne({ _id: volunteer._id }), + VolunteerMembership.deleteMany({ volunteer: volunteer._id }), + ]); return volunteer; }; diff --git a/src/resolvers/Mutation/removeEventVolunteerGroup.ts b/src/resolvers/Mutation/removeEventVolunteerGroup.ts index 74b072c277c..346ff8a4e9c 100644 --- a/src/resolvers/Mutation/removeEventVolunteerGroup.ts +++ b/src/resolvers/Mutation/removeEventVolunteerGroup.ts @@ -1,14 +1,20 @@ import { - EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, + EVENT_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, } from "../../constants"; import { errors, requestContext } from "../../libraries"; -import type { InterfaceUser } from "../../models"; -import { Event, EventVolunteer, EventVolunteerGroup, User } from "../../models"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; +import { + Event, + EventVolunteer, + EventVolunteerGroup, + VolunteerMembership, +} from "../../models"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { adminCheck } from "../../utilities"; +import { + checkUserExists, + checkVolunteerGroupExists, +} from "../../utilities/checks"; /** * This function enables to remove an Event Volunteer Group. @@ -24,59 +30,57 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; export const removeEventVolunteerGroup: MutationResolvers["removeEventVolunteerGroup"] = async (_parent, args, context) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } + const currentUser = await checkUserExists(context.userId); + const volunteerGroup = await checkVolunteerGroupExists(args.id); - if (!currentUser) { + const event = await Event.findById(volunteerGroup.event) + .populate("organization") + .lean(); + if (!event) { throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, + requestContext.translate(EVENT_NOT_FOUND_ERROR.MESSAGE), + EVENT_NOT_FOUND_ERROR.CODE, + EVENT_NOT_FOUND_ERROR.PARAM, ); } - const volunteerGroup = await EventVolunteerGroup.findOne({ - _id: args.id, - }); - - if (!volunteerGroup) { - throw new errors.NotFoundError( - requestContext.translate(EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE), - EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.CODE, - EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.PARAM, - ); - } - - const event = await Event.findById(volunteerGroup.eventId); - - const userIsEventAdmin = event?.admins.some( - (admin) => admin._id.toString() === currentUser?._id.toString(), + const userIsEventAdmin = event.admins.some( + (admin) => admin.toString() === currentUser?._id.toString(), ); - if (!userIsEventAdmin) { - throw new errors.NotFoundError( + const isAdmin = await adminCheck( + currentUser._id, + event.organization, + false, + ); + // Checks if user is Event Admin or Admin of the organization + if (!isAdmin && !userIsEventAdmin) { + throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), USER_NOT_AUTHORIZED_ERROR.CODE, USER_NOT_AUTHORIZED_ERROR.PARAM, ); } - await EventVolunteerGroup.deleteOne({ - _id: args.id, - }); + await Promise.all([ + // Remove the volunteer group + EventVolunteerGroup.deleteOne({ _id: args.id }), + + // Remove the group from volunteers + EventVolunteer.updateMany( + { groups: { $in: args.id } }, + { $pull: { groups: args.id } }, + ), + + // Delete all associated volunteer group memberships + VolunteerMembership.deleteMany({ group: args.id }), - await EventVolunteer.deleteMany({ - groupId: args.id, - }); + // Remove the group from the event + Event.updateOne( + { _id: volunteerGroup.event }, + { $pull: { volunteerGroups: args.id } }, + ), + ]); return volunteerGroup; }; diff --git a/src/resolvers/Mutation/removeFromUserTags.ts b/src/resolvers/Mutation/removeFromUserTags.ts new file mode 100644 index 00000000000..0420f5fbb5c --- /dev/null +++ b/src/resolvers/Mutation/removeFromUserTags.ts @@ -0,0 +1,165 @@ +import { Types } from "mongoose"; +import { + TAG_NOT_FOUND, + USER_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, +} from "../../constants"; +import { errors, requestContext } from "../../libraries"; +import type { InterfaceAppUserProfile, InterfaceUser } from "../../models"; +import { + AppUserProfile, + OrganizationTagUser, + TagUser, + User, +} from "../../models"; +import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; +import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; +import { cacheUsers } from "../../services/UserCache/cacheUser"; +import { findUserInCache } from "../../services/UserCache/findUserInCache"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; + +/** + * This function enables an admin to remove multiple tags from users with a specified tag. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire application + * @remarks The following checks are done: + * 1. If the current user exists and has a profile. + * 2. If the current user is an admin for the organization of the tags. + * 3. If the currentTagId exists and the selected tags exist. + * 4. Remove the tags from users who have the currentTagId. + * @returns Array of tags that were removed from users. + */ +export const removeFromUserTags: MutationResolvers["removeFromUserTags"] = + async (_parent, args, context) => { + let currentUser: InterfaceUser | null; + const userFoundInCache = await findUserInCache([context.userId]); + currentUser = userFoundInCache[0]; + if (currentUser === null) { + currentUser = await User.findOne({ + _id: context.userId, + }).lean(); + if (currentUser !== null) { + await cacheUsers([currentUser]); + } + } + + // Checks whether the currentUser exists. + if (!currentUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + let currentUserAppProfile: InterfaceAppUserProfile | null; + + const appUserProfileFoundInCache = await findAppUserProfileCache([ + currentUser.appUserProfileId.toString(), + ]); + + currentUserAppProfile = appUserProfileFoundInCache[0]; + if (currentUserAppProfile === null) { + currentUserAppProfile = await AppUserProfile.findOne({ + userId: currentUser._id, + }).lean(); + if (currentUserAppProfile !== null) { + await cacheAppUserProfile([currentUserAppProfile]); + } + } + + if (!currentUserAppProfile) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Get the current tag object + const currentTag = await OrganizationTagUser.findOne({ + _id: args.input.currentTagId, + }).lean(); + + if (!currentTag) { + throw new errors.NotFoundError( + requestContext.translate(TAG_NOT_FOUND.MESSAGE), + TAG_NOT_FOUND.CODE, + TAG_NOT_FOUND.PARAM, + ); + } + + // Boolean to determine whether user is an admin of the organization of the current tag. + const currentUserIsOrganizationAdmin = currentUserAppProfile.adminFor.some( + (orgId) => orgId?.toString() === currentTag.organizationId.toString(), + ); + + if ( + !(currentUserIsOrganizationAdmin || currentUserAppProfile.isSuperAdmin) + ) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Find selected tags & all users tagged with the current tag + const [selectedTags, usersWithCurrentTag] = await Promise.all([ + OrganizationTagUser.find({ + _id: { $in: args.input.selectedTagIds }, + }).lean(), + TagUser.find({ tagId: currentTag._id }).lean(), + ]); + + const userIdsWithCurrentTag = usersWithCurrentTag.map( + (userTag) => userTag.userId, + ); + + if (selectedTags.length !== args.input.selectedTagIds.length) { + throw new errors.NotFoundError( + requestContext.translate(TAG_NOT_FOUND.MESSAGE), + TAG_NOT_FOUND.CODE, + TAG_NOT_FOUND.PARAM, + ); + } + + // Get all descendant tags of the selected tags (including the selected tags themselves) + const allTagsToRemove = new Set(); + let currentParents = selectedTags.map((tag) => tag._id.toString()); + + while (currentParents.length > 0) { + // Add the current level of tags to the set + for (const parentId of currentParents) { + allTagsToRemove.add(parentId); + } + + // Find the next level of child tags + const childTags = await OrganizationTagUser.find( + { + parentTagId: { $in: currentParents }, + }, + { _id: 1 }, + ).lean(); + + // Update currentParents with the next level of children + currentParents = childTags.map((tag) => tag._id.toString()); + } + + // Now allTagsToRemove contains all descendants of the selected tags + + const tagUserDocs = userIdsWithCurrentTag.flatMap((userId) => + Array.from(allTagsToRemove).map((tagId) => ({ + deleteOne: { + filter: { userId, tagId: new Types.ObjectId(tagId) }, + }, + })), + ); + + if (tagUserDocs.length > 0) { + await TagUser.bulkWrite(tagUserDocs); + } + + return currentTag; + }; diff --git a/src/resolvers/Mutation/removeGroupChat.ts b/src/resolvers/Mutation/removeGroupChat.ts deleted file mode 100644 index 7509d3fa209..00000000000 --- a/src/resolvers/Mutation/removeGroupChat.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { adminCheck } from "../../utilities"; -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, -} from "../../constants"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { errors, requestContext } from "../../libraries"; -import { GroupChat, GroupChatMessage, Organization } from "../../models"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -/** - * This function enables to remove an graoup chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the group chat exists - * 2. If the organization exists - * 3. If the user is an admin of the organization. - * @returns Deleted group chat. - */ -export const removeGroupChat: MutationResolvers["removeGroupChat"] = async ( - _parent, - args, - context, -) => { - const groupChat = await GroupChat.findOne({ - _id: args.chatId, - }).lean(); - - // Checks if a groupChat with _id === args.chatId exists. - if (!groupChat) { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - let organization; - - const organizationFoundInCache = await findOrganizationsInCache([ - groupChat.organization, - ]); - - organization = organizationFoundInCache[0]; - - if (organizationFoundInCache.includes(null)) { - organization = await Organization.findOne({ - _id: groupChat.organization, - }).lean(); - if (organization) await cacheOrganizations([organization]); - } - - // Checks if an organization with _id === groupChat.organization exists. - if (!organization) { - throw new errors.NotFoundError( - requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - ORGANIZATION_NOT_FOUND_ERROR.CODE, - ORGANIZATION_NOT_FOUND_ERROR.PARAM, - ); - } - - // Checks whether current user making the request is an admin of organization. - await adminCheck(context.userId, organization); - - // Delete all groupChatMessages that have their ids stored in messages list of groupChat - await GroupChatMessage.deleteMany({ - _id: { - $in: groupChat.messages, - }, - }); - - // Delete the groupChat - await GroupChat.deleteOne({ - _id: groupChat._id, - }); - - return groupChat; -}; diff --git a/src/resolvers/Mutation/removePost.ts b/src/resolvers/Mutation/removePost.ts index 96669acd6d1..cfc22f78095 100644 --- a/src/resolvers/Mutation/removePost.ts +++ b/src/resolvers/Mutation/removePost.ts @@ -18,8 +18,7 @@ import { findPostsInCache } from "../../services/PostCache/findPostsInCache"; import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { deletePreviousImage as deleteImage } from "../../utilities/encodedImageStorage/deletePreviousImage"; -import { deletePreviousVideo as deleteVideo } from "../../utilities/encodedVideoStorage/deletePreviousVideo"; +import { deletePreviousFile as deleteFile } from "../../utilities/encodedImageStorage/deletePreviousFile"; import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; /** @@ -125,18 +124,18 @@ export const removePost: MutationResolvers["removePost"] = async ( // Deletes the post. const deletedPost = await Post.findOneAndDelete({ _id: args.id, - }); + }) + .populate({ path: "file", select: "_id metadata.objectKey" }) + .lean(); await deletePostFromCache(args.id); - //deletes the image in post - if (deletedPost?.imageUrl) { - await deleteImage(deletedPost?.imageUrl); - } - - //deletes the video in post - if (deletedPost?.videoUrl) { - await deleteVideo(deletedPost?.videoUrl); + // Deletes the media in the post + if (deletedPost?.file) { + await deleteFile( + deletedPost.file._id.toString(), + deletedPost.file.metadata.objectKey, + ); } // Removes the post from the organization, doesn't fail if the post wasn't pinned diff --git a/src/resolvers/Mutation/removeUserFromGroupChat.ts b/src/resolvers/Mutation/removeUserFromGroupChat.ts deleted file mode 100644 index fea9d35e008..00000000000 --- a/src/resolvers/Mutation/removeUserFromGroupChat.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, -} from "../../constants"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { errors, requestContext } from "../../libraries"; -import { GroupChat, Organization } from "../../models"; -import { adminCheck } from "../../utilities"; -import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations"; -import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache"; -import type { InterfaceGroupChat } from "../../models"; -/** - * This function enables to remove a user from group chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the group chat exists. - * 2. If the organization exists - * 3. If the user is the admin of the organization. - * 4. If the user to be removed is a member of the organization. - * @returns Updated group chat. - */ -export const removeUserFromGroupChat: MutationResolvers["removeUserFromGroupChat"] = - async (_parent, args, context) => { - const groupChat = await GroupChat.findOne({ - _id: args.chatId, - }).lean(); - - // Checks whether groupChat exists. - if (!groupChat) { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - let organization; - - const organizationFoundInCache = await findOrganizationsInCache([ - groupChat.organization, - ]); - - organization = organizationFoundInCache[0]; - - if (organizationFoundInCache[0] == null) { - organization = await Organization.findOne({ - _id: groupChat.organization, - }).lean(); - if (organization) await cacheOrganizations([organization]); - } - - // Checks whether organization exists. - if (!organization) { - throw new errors.NotFoundError( - requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), - ORGANIZATION_NOT_FOUND_ERROR.CODE, - ORGANIZATION_NOT_FOUND_ERROR.PARAM, - ); - } - - // Checks whether currentUser with _id == context.userId is an admin of organzation. - await adminCheck(context.userId, organization); - - const userIsMemberOfGroupChat = groupChat.users.some((user) => - user.equals(args.userId), - ); - - // Checks if user with _id === args.userId is not a member of groupChat. - if (userIsMemberOfGroupChat === false) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - // Removes args.userId from users list of groupChat and returns the updated groupChat. - return (await GroupChat.findOneAndUpdate( - { - _id: args.chatId, - }, - { - $set: { - users: groupChat.users.filter( - (user) => user.toString() !== args.userId.toString(), - ), - }, - }, - { - new: true, - }, - ).lean()) as InterfaceGroupChat; - }; diff --git a/src/resolvers/Mutation/sendMembershipRequest.ts b/src/resolvers/Mutation/sendMembershipRequest.ts index cd4e94767b0..12dadc1587e 100644 --- a/src/resolvers/Mutation/sendMembershipRequest.ts +++ b/src/resolvers/Mutation/sendMembershipRequest.ts @@ -78,8 +78,9 @@ export const sendMembershipRequest: MutationResolvers["sendMembershipRequest"] = // Checks if the user is blocked const user = await User.findById(context.userId).lean(); + if ( - user !== null && + user != null && organization.blockedUsers.some((blockedUser) => new mongoose.Types.ObjectId(blockedUser.toString()).equals(user._id), ) @@ -96,8 +97,24 @@ export const sendMembershipRequest: MutationResolvers["sendMembershipRequest"] = user: context.userId, organization: organization._id, }); - if (membershipRequestExists) { + // Check if the request is already in the user's document + if ( + user != null && + !user.membershipRequests.includes(membershipRequestExists._id) + ) { + // If it's not in the user's document, add it + await User.findByIdAndUpdate( + context.userId, + { + $push: { + membershipRequests: membershipRequestExists._id, + }, + }, + { new: true, runValidators: true }, + ); + } + throw new errors.ConflictError( requestContext.translate(MEMBERSHIP_REQUEST_ALREADY_EXISTS.MESSAGE), MEMBERSHIP_REQUEST_ALREADY_EXISTS.CODE, @@ -105,12 +122,13 @@ export const sendMembershipRequest: MutationResolvers["sendMembershipRequest"] = ); } + // Creating Membership Request const createdMembershipRequest = await MembershipRequest.create({ user: context.userId, organization: organization._id, }); - // add membership request to organization + // Updating Membership Request in organization const updatedOrganization = await Organization.findOneAndUpdate( { _id: organization._id, @@ -129,16 +147,15 @@ export const sendMembershipRequest: MutationResolvers["sendMembershipRequest"] = await cacheOrganizations([updatedOrganization]); } - // add membership request to user - await User.updateOne( - { - _id: context.userId, - }, + // Updating User + await User.findByIdAndUpdate( + context.userId, { $push: { membershipRequests: createdMembershipRequest._id, }, }, + { new: true, runValidators: true }, ); return createdMembershipRequest.toObject(); diff --git a/src/resolvers/Mutation/sendMessageToChat.ts b/src/resolvers/Mutation/sendMessageToChat.ts new file mode 100644 index 00000000000..24145efbd0b --- /dev/null +++ b/src/resolvers/Mutation/sendMessageToChat.ts @@ -0,0 +1,73 @@ +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { errors, requestContext } from "../../libraries"; +import { Chat, User, ChatMessage } from "../../models"; +import { CHAT_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR } from "../../constants"; +/** + * This function enables to send message to chat. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire application + * @remarks The following checks are done: + * 1. If the direct chat exists. + * 2. If the user exists + * @returns Chat message. + */ +export const sendMessageToChat: MutationResolvers["sendMessageToChat"] = async ( + _parent, + args, + context, +) => { + const chat = await Chat.findOne({ + _id: args.chatId, + }).lean(); + + if (!chat) { + throw new errors.NotFoundError( + requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), + CHAT_NOT_FOUND_ERROR.CODE, + CHAT_NOT_FOUND_ERROR.PARAM, + ); + } + + const currentUserExists = !!(await User.exists({ + _id: context.userId, + })); + + if (currentUserExists === false) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + const now = new Date(); + + const createdChatMessage = await ChatMessage.create({ + chatMessageBelongsTo: chat._id, + sender: context.userId, + messageContent: args.messageContent, + replyTo: args.replyTo, + createdAt: now, + updatedAt: now, + }); + + // add createdChatMessage to Chat + await Chat.updateOne( + { + _id: chat._id, + }, + { + $push: { + messages: createdChatMessage._id, + }, + }, + ); + + // calls subscription + context.pubsub.publish("MESSAGE_SENT_TO_CHAT", { + messageSentToChat: createdChatMessage.toObject(), + }); + + return createdChatMessage.toObject(); +}; diff --git a/src/resolvers/Mutation/sendMessageToDirectChat.ts b/src/resolvers/Mutation/sendMessageToDirectChat.ts deleted file mode 100644 index 65cbf15f2bc..00000000000 --- a/src/resolvers/Mutation/sendMessageToDirectChat.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { errors, requestContext } from "../../libraries"; -import { DirectChat, DirectChatMessage, User } from "../../models"; -import { CHAT_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR } from "../../constants"; -/** - * This function enables to send message to direct chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the direct chat exists. - * 2. If the user exists - * @returns Direct chat message. - */ -export const sendMessageToDirectChat: MutationResolvers["sendMessageToDirectChat"] = - async (_parent, args, context) => { - const directChat = await DirectChat.findOne({ - _id: args.chatId, - }).lean(); - - if (!directChat) { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - const currentUserExists = !!(await User.exists({ - _id: context.userId, - })); - - if (currentUserExists === false) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - // directChat.users can only have 2 users. So, the following method works. - const receiverIndex = directChat.users.findIndex( - (user) => user.toString() !== context.userId.toString(), - ); - - const createdDirectChatMessage = await DirectChatMessage.create({ - directChatMessageBelongsTo: directChat._id, - sender: context.userId, - receiver: directChat.users[receiverIndex], - messageContent: args.messageContent, - }); - - // add createdDirectChatMessage to directChat - await DirectChat.updateOne( - { - _id: directChat._id, - }, - { - $push: { - messages: createdDirectChatMessage._id, - }, - }, - ); - - // calls subscription - context.pubsub.publish("MESSAGE_SENT_TO_DIRECT_CHAT", { - messageSentToDirectChat: createdDirectChatMessage.toObject(), - }); - - return createdDirectChatMessage.toObject(); - }; diff --git a/src/resolvers/Mutation/sendMessageToGroupChat.ts b/src/resolvers/Mutation/sendMessageToGroupChat.ts deleted file mode 100644 index 93d9f7d5dd1..00000000000 --- a/src/resolvers/Mutation/sendMessageToGroupChat.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { errors, requestContext } from "../../libraries"; -import { GroupChat, GroupChatMessage, User } from "../../models"; -import { - USER_NOT_AUTHORIZED_ERROR, - CHAT_NOT_FOUND_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -/** - * This function enables to send message to group chat. - * @param _parent - parent of current request - * @param args - payload provided with the request - * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the group chat exists. - * 2. If the user exists - * 3. If the group chat contains the user. - * @returns Group chat message. - */ -export const sendMessageToGroupChat: MutationResolvers["sendMessageToGroupChat"] = - async (_parent, args, context) => { - const groupChat = await GroupChat.findOne({ - _id: args.chatId, - }).lean(); - - if (!groupChat) { - throw new errors.NotFoundError( - requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - const currentUserExists = !!(await User.exists({ - _id: context.userId, - })); - - if (currentUserExists === false) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - const currentUserIsAMemberOfGroupChat = groupChat.users.some((user) => - user.equals(context.userId), - ); - - /* - checks if users list of groupChat with _id === args.chatId contains - current user with _id === context.userId - */ - if (currentUserIsAMemberOfGroupChat === false) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - const createdGroupChatMessage = await GroupChatMessage.create({ - groupChatMessageBelongsTo: groupChat._id, - sender: context.userId, - createdAt: new Date(), - messageContent: args.messageContent, - }); - - // add createdGroupChatMessage to groupChat - await GroupChat.updateOne( - { - _id: args.chatId, - }, - { - $push: { - messages: createdGroupChatMessage._id, - }, - }, - ); - - // calls subscription - context.pubsub.publish("MESSAGE_SENT_TO_GROUP_CHAT", { - messageSentToGroupChat: createdGroupChatMessage.toObject(), - }); - - return createdGroupChatMessage.toObject(); - }; diff --git a/src/resolvers/Mutation/updateActionItem.ts b/src/resolvers/Mutation/updateActionItem.ts index 265a40104c4..555cb824337 100644 --- a/src/resolvers/Mutation/updateActionItem.ts +++ b/src/resolvers/Mutation/updateActionItem.ts @@ -2,45 +2,54 @@ import mongoose from "mongoose"; import { ACTION_ITEM_NOT_FOUND_ERROR, EVENT_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, USER_NOT_FOUND_ERROR, - USER_NOT_MEMBER_FOR_ORGANIZATION, } from "../../constants"; import { errors, requestContext } from "../../libraries"; import type { - InterfaceAppUserProfile, InterfaceEvent, + InterfaceEventVolunteer, + InterfaceEventVolunteerGroup, InterfaceUser, } from "../../models"; -import { ActionItem, AppUserProfile, Event, User } from "../../models"; -import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; -import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; +import { + ActionItem, + Event, + EventVolunteer, + EventVolunteerGroup, + User, +} from "../../models"; import { cacheEvents } from "../../services/EventCache/cacheEvents"; import { findEventsInCache } from "../../services/EventCache/findEventInCache"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { + checkAppUserProfileExists, + checkUserExists, +} from "../../utilities/checks"; /** * This function enables to update an action item. * @param _parent - parent of current request * @param args - payload provided with the request * @param context - context of entire application * @remarks The following checks are done: - * 1. If the user exists. - * 2. If the new asignee exists. - * 2. If the action item exists. - * 4. If the new asignee is a member of the organization. - * 5. If the user is authorized. - * 6. If the user has appUserProfile. + * 1. Whether the user exists + * 2. Whether the user has an associated app user profile + * 3. Whether the action item exists + * 4. Whether the user is authorized to update the action item + * 5. Whether the user is an admin of the organization or a superadmin + * * @returns Updated action item. */ type UpdateActionItemInputType = { assigneeId: string; + assigneeType: string; preCompletionNotes: string; postCompletionNotes: string; dueDate: Date; - allotedHours: number; + allottedHours: number; completionDate: Date; isCompleted: boolean; }; @@ -50,46 +59,9 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( args, context, ) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - - // Checks if the user exists - if (currentUser === null) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - let currentUserAppProfile: InterfaceAppUserProfile | null; - const appUserProfileFoundInCache = await findAppUserProfileCache([ - currentUser.appUserProfileId?.toString(), - ]); - currentUserAppProfile = appUserProfileFoundInCache[0]; - if (currentUserAppProfile === null) { - currentUserAppProfile = await AppUserProfile.findOne({ - userId: currentUser._id, - }).lean(); - if (currentUserAppProfile !== null) { - await cacheAppUserProfile([currentUserAppProfile]); - } - } - if (!currentUserAppProfile) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } + const currentUser = await checkUserExists(context.userId); + const currentUserAppProfile = await checkAppUserProfileExists(currentUser); + const { assigneeId, assigneeType, isCompleted } = args.data; const actionItem = await ActionItem.findOne({ _id: args.id, @@ -106,44 +78,54 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( ); } - let sameAssignedUser = false; + let sameAssignee = false; - if (args.data.assigneeId) { - sameAssignedUser = new mongoose.Types.ObjectId( - actionItem.assignee.toString(), - ).equals(args.data.assigneeId); + if (assigneeId) { + sameAssignee = new mongoose.Types.ObjectId( + assigneeType === "EventVolunteer" + ? actionItem.assignee.toString() + : assigneeType === "EventVolunteerGroup" + ? actionItem.assigneeGroup.toString() + : actionItem.assigneeUser.toString(), + ).equals(assigneeId); - if (!sameAssignedUser) { - const newAssignedUser = await User.findOne({ - _id: args.data.assigneeId, - }); - - // Checks if the new asignee exists - if (newAssignedUser === null) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - let userIsOrganizationMember = false; - const currorganizationId = actionItem.actionItemCategory.organizationId; - userIsOrganizationMember = newAssignedUser.joinedOrganizations.some( - (organizationId) => - organizationId === currorganizationId || - new mongoose.Types.ObjectId(organizationId.toString()).equals( - currorganizationId, - ), - ); - - // Checks if the new asignee is a member of the organization - if (!userIsOrganizationMember) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE), - USER_NOT_MEMBER_FOR_ORGANIZATION.CODE, - USER_NOT_MEMBER_FOR_ORGANIZATION.PARAM, - ); + if (!sameAssignee) { + let assignee: + | InterfaceEventVolunteer + | InterfaceEventVolunteerGroup + | InterfaceUser + | null; + if (assigneeType === "EventVolunteer") { + assignee = await EventVolunteer.findById(assigneeId) + .populate("user") + .lean(); + if (!assignee) { + throw new errors.NotFoundError( + requestContext.translate(EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE), + EVENT_VOLUNTEER_NOT_FOUND_ERROR.CODE, + EVENT_VOLUNTEER_NOT_FOUND_ERROR.PARAM, + ); + } + } else if (assigneeType === "EventVolunteerGroup") { + assignee = await EventVolunteerGroup.findById(assigneeId).lean(); + if (!assignee) { + throw new errors.NotFoundError( + requestContext.translate( + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE, + ), + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.CODE, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.PARAM, + ); + } + } else if (assigneeType === "User") { + assignee = await User.findById(assigneeId).lean(); + if (!assignee) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } } } } @@ -192,8 +174,9 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( ); } - // Checks if the user is authorized for the operation. + // Checks if the user is authorized for the operation. (Exception: when user updates the action item to complete or incomplete) if ( + isCompleted === undefined && currentUserIsEventAdmin === false && currentUserIsOrgAdmin === false && currentUserAppProfile.isSuperAdmin === false @@ -205,13 +188,102 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( ); } - const updatedAssignmentDate = sameAssignedUser + // checks if the assignee is an event volunteer then add allotted hours to the volunteer else if event volunteer group then add divided equal allotted hours to all volunteers in the group + + if (assigneeType === "EventVolunteer") { + const assignee = await EventVolunteer.findById(assigneeId).lean(); + if (assignee) { + if (isCompleted == true) { + await EventVolunteer.findByIdAndUpdate(assigneeId, { + $inc: { + hoursVolunteered: actionItem.allottedHours + ? actionItem.allottedHours + : 0, + }, + ...(actionItem.allottedHours + ? { + $push: { + hoursHistory: { + hours: actionItem.allottedHours, + date: new Date(), + }, + }, + } + : {}), + }); + } else if (isCompleted == false) { + await EventVolunteer.findByIdAndUpdate(assigneeId, { + $inc: { + hoursVolunteered: actionItem.allottedHours + ? -actionItem.allottedHours + : -0, + }, + ...(actionItem.allottedHours + ? { + $push: { + hoursHistory: { + hours: -actionItem.allottedHours, + date: new Date(), + }, + }, + } + : {}), + }); + } + } + } else if (assigneeType === "EventVolunteerGroup") { + const volunteerGroup = + await EventVolunteerGroup.findById(assigneeId).lean(); + if (volunteerGroup) { + const dividedHours = + (actionItem.allottedHours ?? 0) / volunteerGroup.volunteers.length; + if (isCompleted == true) { + await EventVolunteer.updateMany( + { _id: { $in: volunteerGroup.volunteers } }, + { + $inc: { + hoursVolunteered: dividedHours, + }, + ...(dividedHours + ? { + $push: { + hoursHistory: { + hours: dividedHours, + date: new Date(), + }, + }, + } + : {}), + }, + ); + } else if (isCompleted == false) { + await EventVolunteer.updateMany( + { _id: { $in: volunteerGroup.volunteers } }, + { + $inc: { + hoursVolunteered: -dividedHours, + }, + ...(dividedHours + ? { + $push: { + hoursHistory: { + hours: dividedHours, + date: new Date(), + }, + }, + } + : {}), + }, + ); + } + } + } + + const updatedAssignmentDate = sameAssignee ? actionItem.assignmentDate : new Date(); - const updatedAssigner = sameAssignedUser - ? actionItem.assigner - : context.userId; + const updatedAssigner = sameAssignee ? actionItem.assigner : context.userId; const updatedActionItem = await ActionItem.findOneAndUpdate( { @@ -219,7 +291,25 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( }, { ...(args.data as UpdateActionItemInputType), - assignee: args.data.assigneeId || actionItem.assignee, + assigneeType: assigneeType || actionItem.assigneeType, + assignee: + !sameAssignee && assigneeType === "EventVolunteer" + ? assigneeId || actionItem.assignee + : isCompleted === undefined + ? null + : actionItem.assignee, + assigneeGroup: + !sameAssignee && assigneeType === "EventVolunteerGroup" + ? assigneeId || actionItem.assigneeGroup + : isCompleted === undefined + ? null + : actionItem.assigneeGroup, + assigneeUser: + !sameAssignee && assigneeType === "User" + ? assigneeId || actionItem.assigneeUser + : isCompleted === undefined + ? null + : actionItem.assigneeUser, assignmentDate: updatedAssignmentDate, assigner: updatedAssigner, }, diff --git a/src/resolvers/Mutation/updateAgendaCategory.ts b/src/resolvers/Mutation/updateAgendaCategory.ts index cf2c5239305..82105f8c643 100644 --- a/src/resolvers/Mutation/updateAgendaCategory.ts +++ b/src/resolvers/Mutation/updateAgendaCategory.ts @@ -117,7 +117,7 @@ export const updateAgendaCategory: MutationResolvers["updateAgendaCategory"] = { $set: { updatedBy: context.userId, - // eslint-disable-next-line + ...(args.input as UpdateAgendaCategoryInput), }, }, diff --git a/src/resolvers/Mutation/updateEventVolunteer.ts b/src/resolvers/Mutation/updateEventVolunteer.ts index 68d5cdbdbbd..6aa7946b1f4 100644 --- a/src/resolvers/Mutation/updateEventVolunteer.ts +++ b/src/resolvers/Mutation/updateEventVolunteer.ts @@ -1,15 +1,12 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import type { EventVolunteerResponse } from "../../constants"; -import { - EVENT_VOLUNTEER_INVITE_USER_MISTMATCH, - EVENT_VOLUNTEER_NOT_FOUND_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import type { InterfaceEventVolunteer, InterfaceUser } from "../../models"; -import { User, EventVolunteer } from "../../models"; +import { EVENT_VOLUNTEER_INVITE_USER_MISTMATCH } from "../../constants"; +import type { InterfaceEventVolunteer } from "../../models"; +import { EventVolunteer } from "../../models"; import { errors, requestContext } from "../../libraries"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; +import { + checkEventVolunteerExists, + checkUserExists, +} from "../../utilities/checks"; /** * This function enables to update an Event Volunteer * @param _parent - parent of current request @@ -19,43 +16,14 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; * 1. Whether the user exists * 2. Whether the EventVolunteer exists * 3. Whether the current user is the user of EventVolunteer - * 4. Whether the EventVolunteer is invited + * 4. Update the EventVolunteer */ export const updateEventVolunteer: MutationResolvers["updateEventVolunteer"] = async (_parent, args, context) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - - if (!currentUser) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - const eventVolunteer = await EventVolunteer.findOne({ - _id: args.id, - }).lean(); - - if (!eventVolunteer) { - throw new errors.NotFoundError( - requestContext.translate(EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE), - EVENT_VOLUNTEER_NOT_FOUND_ERROR.CODE, - EVENT_VOLUNTEER_NOT_FOUND_ERROR.PARAM, - ); - } + await checkUserExists(context.userId); + const volunteer = await checkEventVolunteerExists(args.id); - if (eventVolunteer.userId.toString() !== context.userId.toString()) { + if (volunteer.user.toString() !== context.userId.toString()) { throw new errors.ConflictError( requestContext.translate(EVENT_VOLUNTEER_INVITE_USER_MISTMATCH.MESSAGE), EVENT_VOLUNTEER_INVITE_USER_MISTMATCH.CODE, @@ -69,22 +37,18 @@ export const updateEventVolunteer: MutationResolvers["updateEventVolunteer"] = }, { $set: { - eventId: - args.data?.eventId === undefined - ? eventVolunteer.eventId - : (args?.data.eventId as string), - isAssigned: - args.data?.isAssigned === undefined - ? eventVolunteer.isAssigned - : (args.data?.isAssigned as boolean), - isInvited: - args.data?.isInvited === undefined - ? eventVolunteer.isInvited - : (args.data?.isInvited as boolean), - response: - args.data?.response === undefined - ? eventVolunteer.response - : (args.data?.response as EventVolunteerResponse), + assignments: + args.data?.assignments === undefined + ? volunteer.assignments + : (args.data?.assignments as string[]), + hasAccepted: + args.data?.hasAccepted === undefined + ? volunteer.hasAccepted + : (args.data?.hasAccepted as boolean), + isPublic: + args.data?.isPublic === undefined + ? volunteer.isPublic + : (args.data?.isPublic as boolean), }, }, { diff --git a/src/resolvers/Mutation/updateEventVolunteerGroup.ts b/src/resolvers/Mutation/updateEventVolunteerGroup.ts index e7c67290d44..1abb7112f9e 100644 --- a/src/resolvers/Mutation/updateEventVolunteerGroup.ts +++ b/src/resolvers/Mutation/updateEventVolunteerGroup.ts @@ -1,14 +1,14 @@ import { + EVENT_NOT_FOUND_ERROR, EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, } from "../../constants"; import { errors, requestContext } from "../../libraries"; -import type { InterfaceEventVolunteerGroup, InterfaceUser } from "../../models"; -import { EventVolunteerGroup, User } from "../../models"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; +import type { InterfaceEventVolunteerGroup } from "../../models"; +import { Event, EventVolunteerGroup } from "../../models"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { adminCheck } from "../../utilities"; +import { checkUserExists } from "../../utilities/checks"; /** * This function enables to update the Event Volunteer Group * @param _parent - parent of current request @@ -21,26 +21,28 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; */ export const updateEventVolunteerGroup: MutationResolvers["updateEventVolunteerGroup"] = async (_parent, args, context) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - - if (!currentUser) { + const { eventId, description, name, volunteersRequired } = args.data; + const currentUser = await checkUserExists(context.userId); + const event = await Event.findById(eventId).populate("organization").lean(); + if (!event) { throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, + requestContext.translate(EVENT_NOT_FOUND_ERROR.MESSAGE), + EVENT_NOT_FOUND_ERROR.CODE, + EVENT_NOT_FOUND_ERROR.PARAM, ); } + const userIsEventAdmin = event.admins.some( + (admin: { toString: () => string }) => + admin.toString() === currentUser?._id.toString(), + ); + + const isAdmin = await adminCheck( + currentUser._id, + event.organization, + false, + ); + const group = await EventVolunteerGroup.findOne({ _id: args.id, }).lean(); @@ -53,7 +55,12 @@ export const updateEventVolunteerGroup: MutationResolvers["updateEventVolunteerG ); } - if (group.leaderId.toString() !== context.userId.toString()) { + // Checks if user is Event Admin or Admin of the organization or Leader of the group + if ( + !isAdmin && + !userIsEventAdmin && + group.leader.toString() !== currentUser._id.toString() + ) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), USER_NOT_AUTHORIZED_ERROR.CODE, @@ -67,20 +74,13 @@ export const updateEventVolunteerGroup: MutationResolvers["updateEventVolunteerG }, { $set: { - eventId: - args.data?.eventId === undefined - ? group.eventId - : args?.data.eventId, - name: args.data?.name === undefined ? group.name : args?.data.name, - volunteersRequired: - args.data?.volunteersRequired === undefined - ? group.volunteersRequired - : args?.data.volunteersRequired, + description, + name, + volunteersRequired, }, }, { new: true, - runValidators: true, }, ).lean(); diff --git a/src/resolvers/Mutation/updatePost.ts b/src/resolvers/Mutation/updatePost.ts deleted file mode 100644 index 82d3669a20d..00000000000 --- a/src/resolvers/Mutation/updatePost.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { Types } from "mongoose"; -import { - LENGTH_VALIDATION_ERROR, - PLEASE_PROVIDE_TITLE, - POST_NEEDS_TO_BE_PINNED, - POST_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import { isValidString } from "../../libraries/validators/validateString"; -import type { - InterfaceAppUserProfile, - InterfacePost, - InterfaceUser, -} from "../../models"; -import { AppUserProfile, Post, User } from "../../models"; -import { cachePosts } from "../../services/PostCache/cachePosts"; -import { findPostsInCache } from "../../services/PostCache/findPostsInCache"; -import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; -import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; -import { uploadEncodedVideo } from "../../utilities/encodedVideoStorage/uploadEncodedVideo"; -import { findUserInCache } from "../../services/UserCache/findUserInCache"; -import { cacheUsers } from "../../services/UserCache/cacheUser"; -import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; -import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; - -/** - * Updates a post with new details, including handling image and video URL uploads and validating input fields. - * - * This function updates an existing post based on the provided input. It retrieves and validates the current user and their app profile, checks if the user has the necessary permissions, handles media file uploads, and performs input validation before updating the post in the database. The function returns the updated post after applying changes. - * - * @param _parent - This parameter represents the parent resolver in the GraphQL schema and is not used in this function. - * @param args - The arguments passed to the GraphQL mutation, including the post's `id` and data to update, such as `title`, `text`, `imageUrl`, and `videoUrl`. - * @param context - Provides contextual information, including the current user's ID. This is used to authenticate and authorize the request. - * - * @returns The updated post with all its fields. - */ -export const updatePost: MutationResolvers["updatePost"] = async ( - _parent, - args, - context, -) => { - let currentUser: InterfaceUser | null; - const userFoundInCache = await findUserInCache([context.userId]); - currentUser = userFoundInCache[0]; - if (currentUser === null) { - currentUser = await User.findOne({ - _id: context.userId, - }).lean(); - if (currentUser !== null) { - await cacheUsers([currentUser]); - } - } - - if (!currentUser) { - throw new errors.NotFoundError( - requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), - USER_NOT_FOUND_ERROR.CODE, - USER_NOT_FOUND_ERROR.PARAM, - ); - } - - let currentUserAppProfile: InterfaceAppUserProfile | null; - const appUserProfileFoundInCache = await findAppUserProfileCache([ - currentUser.appUserProfileId?.toString(), - ]); - currentUserAppProfile = appUserProfileFoundInCache[0]; - if (currentUserAppProfile === null) { - currentUserAppProfile = await AppUserProfile.findOne({ - userId: currentUser._id, - }).lean(); - if (currentUserAppProfile !== null) { - await cacheAppUserProfile([currentUserAppProfile]); - } - } - if (!currentUserAppProfile) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - let post: InterfacePost | null; - - const postFoundInCache = await findPostsInCache([args.id]); - - post = postFoundInCache[0]; - - if (postFoundInCache[0] === null) { - post = await Post.findOne({ - _id: args.id, - }).lean(); - if (post !== null) { - await cachePosts([post]); - } - } - - // Check if the post exists - if (!post) { - throw new errors.NotFoundError( - requestContext.translate(POST_NOT_FOUND_ERROR.MESSAGE), - POST_NOT_FOUND_ERROR.CODE, - POST_NOT_FOUND_ERROR.PARAM, - ); - } - - // Check if the user has the right to update the post - const currentUserIsPostCreator = post.creatorId.equals(context.userId); - const isSuperAdmin = currentUserAppProfile.isSuperAdmin; - const isAdminOfPostOrganization = currentUserAppProfile?.adminFor.some( - (orgID) => - orgID && new Types.ObjectId(orgID?.toString()).equals(post?.organization), - ); - - // checks if current user is an creator of the post with _id === args.id - if ( - !currentUserIsPostCreator && - !isAdminOfPostOrganization && - !isSuperAdmin - ) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - // Handle image and video URL uploads - if (args.data?.imageUrl && args.data?.imageUrl !== null) { - args.data.imageUrl = await uploadEncodedImage( - args.data.imageUrl, - post.imageUrl, - ); - } - - if (args.data?.videoUrl && args.data?.videoUrl !== null) { - args.data.videoUrl = await uploadEncodedVideo( - args.data.videoUrl, - post.videoUrl, - ); - } - - // Validate title and pinned status - if (args.data?.title && !post.pinned) { - throw new errors.InputValidationError( - requestContext.translate(POST_NEEDS_TO_BE_PINNED.MESSAGE), - POST_NEEDS_TO_BE_PINNED.CODE, - ); - } else if (!args.data?.title && post.pinned) { - throw new errors.InputValidationError( - requestContext.translate(PLEASE_PROVIDE_TITLE.MESSAGE), - PLEASE_PROVIDE_TITLE.CODE, - ); - } - - // Validate input lengths - const validationResultTitle = isValidString(args.data?.title ?? "", 256); - const validationResultText = isValidString(args.data?.text ?? "", 500); - if (!validationResultTitle.isLessThanMaxLength) { - throw new errors.InputValidationError( - requestContext.translate( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, - ), - LENGTH_VALIDATION_ERROR.CODE, - ); - } - if (!validationResultText.isLessThanMaxLength) { - throw new errors.InputValidationError( - requestContext.translate( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in information`, - ), - LENGTH_VALIDATION_ERROR.CODE, - ); - } - - // Update the post in the database - const updatedPost = await Post.findOneAndUpdate( - { - _id: args.id, - }, - { - ...(args.data as Record), - }, - { - new: true, - }, - ).lean(); - - if (updatedPost !== null) { - await cachePosts([updatedPost]); - } - - return updatedPost as InterfacePost; -}; diff --git a/src/resolvers/Mutation/updateSessionTimeout.ts b/src/resolvers/Mutation/updateSessionTimeout.ts new file mode 100644 index 00000000000..67fa92d536b --- /dev/null +++ b/src/resolvers/Mutation/updateSessionTimeout.ts @@ -0,0 +1,89 @@ +import type { InterfaceAppUserProfile } from "../../models"; +import { User, AppUserProfile } from "../../models"; +import { + COMMUNITY_NOT_FOUND_ERROR, + INVALID_TIMEOUT_RANGE, + USER_NOT_FOUND_ERROR, + APP_USER_PROFILE_NOT_FOUND_ERROR, + MINIMUM_TIMEOUT_MINUTES, + MAXIMUM_TIMEOUT_MINUTES, +} from "../../constants"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { errors, requestContext } from "../../libraries"; +import { superAdminCheck } from "../../utilities"; +import { Community } from "../../models/Community"; + +/** + * This function updates the session timeout and can only be performed by superadmin users. + * @param _parent - parent of the current request + * @param args - payload provided with the request, including organizationId and timeout + * @param context - context of the entire application, containing user information + * @returns - A message true if the organization timeout is updated successfully + * @throws - NotFoundError: If the user, appuserprofile or organization is not found + * @throws - ValidationError: If the user is not an admin or superadmin, or if the timeout is outside the valid range + * @throws - InternalServerError: If there is an error updating the organization timeout + * + */ + +export const updateSessionTimeout: MutationResolvers["updateSessionTimeout"] = + async (_parent, args, context) => { + const userId = context.userId; + const user = await User.findById(userId).lean(); + + if (!user) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + + //const appuserprofile: InterfaceAppUserProfile | null = await AppUserProfile.findOne({userId: userId}).lean(); + const appuserprofile: InterfaceAppUserProfile | null = + await AppUserProfile.findById(user.appUserProfileId).lean(); //more appropriate since it shows the link between the user and the userprofile + + if (!appuserprofile) { + throw new errors.NotFoundError( + requestContext.translate(APP_USER_PROFILE_NOT_FOUND_ERROR.MESSAGE), + APP_USER_PROFILE_NOT_FOUND_ERROR.CODE, + APP_USER_PROFILE_NOT_FOUND_ERROR.PARAM, + ); + } + + superAdminCheck(appuserprofile); + + const community = await Community.findOne().lean(); + + if (!community) { + throw new errors.NotFoundError( + requestContext.translate(COMMUNITY_NOT_FOUND_ERROR.MESSAGE), + COMMUNITY_NOT_FOUND_ERROR.CODE, + COMMUNITY_NOT_FOUND_ERROR.PARAM, + ); + } + + if ( + args.timeout < MINIMUM_TIMEOUT_MINUTES || + args.timeout > MAXIMUM_TIMEOUT_MINUTES || + args.timeout % 5 !== 0 + ) { + throw new errors.ValidationError( + [ + { + message: requestContext.translate(INVALID_TIMEOUT_RANGE.MESSAGE), + code: INVALID_TIMEOUT_RANGE.CODE, + param: INVALID_TIMEOUT_RANGE.PARAM, + }, + ], + INVALID_TIMEOUT_RANGE.MESSAGE, + ); + } + + await Community.findByIdAndUpdate( + community._id, + { timeout: args.timeout }, + { new: true }, + ); + + return true; + }; diff --git a/src/resolvers/Mutation/updateUserProfile.ts b/src/resolvers/Mutation/updateUserProfile.ts index ee8c0e36acf..62c1e602e21 100644 --- a/src/resolvers/Mutation/updateUserProfile.ts +++ b/src/resolvers/Mutation/updateUserProfile.ts @@ -142,7 +142,7 @@ export const updateUserProfile: MutationResolvers["updateUserProfile"] = async ( }, ).lean(); if (updatedUser != null) { - await deleteUserFromCache(updatedUser?._id.toString() || ""); + await deleteUserFromCache(updatedUser?._id.toString()); await cacheUsers([updatedUser]); } @@ -159,11 +159,6 @@ export const updateUserProfile: MutationResolvers["updateUserProfile"] = async ( ); } - if (updatedUser != null) { - updatedUser.image = updatedUser?.image - ? `${context.apiRootUrl}${updatedUser?.image}` - : null; - } if (args.data == undefined) updatedUser = null; return updatedUser ?? ({} as InterfaceUser); diff --git a/src/resolvers/Mutation/updateVolunteerMembership.ts b/src/resolvers/Mutation/updateVolunteerMembership.ts new file mode 100644 index 00000000000..18d28e483d9 --- /dev/null +++ b/src/resolvers/Mutation/updateVolunteerMembership.ts @@ -0,0 +1,137 @@ +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import type { + InterfaceEvent, + InterfaceEventVolunteerGroup, + InterfaceVolunteerMembership, +} from "../../models"; +import { + Event, + EventVolunteer, + EventVolunteerGroup, + VolunteerMembership, +} from "../../models"; +import { + checkUserExists, + checkVolunteerMembershipExists, +} from "../../utilities/checks"; +import { adminCheck } from "../../utilities"; +import { errors, requestContext } from "../../libraries"; +import { USER_NOT_AUTHORIZED_ERROR } from "../../constants"; + +/** + * Helper function to handle updates when status is accepted + */ +const handleAcceptedStatusUpdates = async ( + membership: InterfaceVolunteerMembership, +): Promise => { + const updatePromises = []; + + // Always update EventVolunteer to set hasAccepted to true + updatePromises.push( + EventVolunteer.findOneAndUpdate( + { _id: membership.volunteer, event: membership.event }, + { + $set: { hasAccepted: true }, + ...(membership.group && { $push: { groups: membership.group } }), + }, + ), + ); + + // Always update Event to add volunteer + updatePromises.push( + Event.findOneAndUpdate( + { _id: membership.event }, + { $addToSet: { volunteers: membership.volunteer } }, + ), + ); + + // If group exists, update the EventVolunteerGroup as well + if (membership.group) { + updatePromises.push( + EventVolunteerGroup.findOneAndUpdate( + { _id: membership.group }, + { $addToSet: { volunteers: membership.volunteer } }, + ), + ); + } + + // Execute all updates in parallel + await Promise.all(updatePromises); +}; + +/** + * This function enables to update an Volunteer Membership + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire application + * @remarks The following checks are done: + * 1. Whether the user exists + * 2. Update the Volunteer Membership + * 3. update related fields of Volunteer Group & Volunteer + */ +export const updateVolunteerMembership: MutationResolvers["updateVolunteerMembership"] = + async (_parent, args, context) => { + const currentUser = await checkUserExists(context.userId); + const volunteerMembership = await checkVolunteerMembershipExists(args.id); + + const event = (await Event.findById(volunteerMembership.event) + .populate("organization") + .lean()) as InterfaceEvent; + + if (volunteerMembership.status != "invited") { + // Check if the user is authorized to update the volunteer membership + const isAdminOrSuperAdmin = await adminCheck( + currentUser._id, + event.organization, + false, + ); + const isEventAdmin = event.admins.some( + (admin) => admin.toString() == currentUser._id.toString(), + ); + let isGroupLeader = false; + if (volunteerMembership.group != undefined) { + // check if current user is group leader + const group = (await EventVolunteerGroup.findById( + volunteerMembership.group, + ).lean()) as InterfaceEventVolunteerGroup; + isGroupLeader = group.leader.toString() == currentUser._id.toString(); + } + + // If the user is not an admin or super admin, event admin, or group leader, throw an error + if (!isAdminOrSuperAdmin && !isEventAdmin && !isGroupLeader) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + } + + const updatedVolunteerMembership = + (await VolunteerMembership.findOneAndUpdate( + { + _id: args.id, + }, + { + $set: { + status: args.status as + | "invited" + | "requested" + | "accepted" + | "rejected", + updatedBy: context.userId, + }, + }, + { + new: true, + runValidators: true, + }, + ).lean()) as InterfaceVolunteerMembership; + + // Handle additional updates if the status is accepted + if (args.status === "accepted") { + await handleAcceptedStatusUpdates(updatedVolunteerMembership); + } + + return updatedVolunteerMembership; + }; diff --git a/src/resolvers/Organization/image.ts b/src/resolvers/Organization/image.ts index b94afa9975c..fed5e163b69 100644 --- a/src/resolvers/Organization/image.ts +++ b/src/resolvers/Organization/image.ts @@ -12,13 +12,9 @@ import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. * */ -export const image: OrganizationResolvers["image"] = ( - parent, - _args, - context, -) => { +export const image: OrganizationResolvers["image"] = (parent) => { if (parent.image) { - return `${context.apiRootUrl}${parent.image}`; + return parent.image; } return null; }; diff --git a/src/resolvers/Organization/posts.ts b/src/resolvers/Organization/posts.ts index 55f0ba9caeb..9f3b37334f6 100644 --- a/src/resolvers/Organization/posts.ts +++ b/src/resolvers/Organization/posts.ts @@ -36,11 +36,7 @@ import { MAXIMUM_FETCH_LIMIT } from "../../constants"; * @see OrganizationResolvers - The type definition for the resolvers of the Organization fields. * */ -export const posts: OrganizationResolvers["posts"] = async ( - parent, - args, - context, -) => { +export const posts: OrganizationResolvers["posts"] = async (parent, args) => { const parseGraphQLConnectionArgumentsResult = await parseGraphQLConnectionArguments({ args, @@ -79,6 +75,13 @@ export const posts: OrganizationResolvers["posts"] = async ( }) .sort(sort) .limit(parsedArgs.limit) + .populate([ + { + path: "likedBy", + select: "image firstName lastName", + }, + { path: "file" }, + ]) .lean() .exec(), @@ -88,21 +91,12 @@ export const posts: OrganizationResolvers["posts"] = async ( .countDocuments() .exec(), ]); - const posts = objectList.map((post: InterfacePost) => ({ - ...post, - imageUrl: post.imageUrl - ? new URL(post.imageUrl, context.apiRootUrl).toString() - : null, - videoUrl: post.videoUrl - ? new URL(post.videoUrl, context.apiRootUrl).toString() - : null, - })); return transformToDefaultGraphQLConnection< ParsedCursor, InterfacePost, InterfacePost >({ - objectList: posts, + objectList, parsedArgs, totalCount, }); diff --git a/src/resolvers/Organization/userTags.ts b/src/resolvers/Organization/userTags.ts index 2b4160cf363..c82cceaef7c 100644 --- a/src/resolvers/Organization/userTags.ts +++ b/src/resolvers/Organization/userTags.ts @@ -2,9 +2,7 @@ import type { OrganizationResolvers } from "../../types/generatedGraphQLTypes"; import type { InterfaceOrganizationTagUser } from "../../models"; import { OrganizationTagUser } from "../../models"; import { - getCommonGraphQLConnectionFilter, - getCommonGraphQLConnectionSort, - parseGraphQLConnectionArguments, + parseGraphQLConnectionArgumentsWithSortedByAndWhere, transformToDefaultGraphQLConnection, type DefaultGraphQLArgumentError, type ParseGraphQLConnectionCursorArguments, @@ -13,6 +11,12 @@ import { import { GraphQLError } from "graphql"; import { MAXIMUM_FETCH_LIMIT } from "../../constants"; import type { Types } from "mongoose"; +import { + getUserTagGraphQLConnectionFilter, + getUserTagGraphQLConnectionSort, + parseUserTagSortedBy, + parseUserTagWhere, +} from "../../utilities/userTagsPaginationUtils"; /** * Resolver function for the `userTags` field of an `Organization`. @@ -37,14 +41,20 @@ export const userTags: OrganizationResolvers["userTags"] = async ( parent, args, ) => { + const parseWhereResult = parseUserTagWhere(args.where); + const parseSortedByResult = parseUserTagSortedBy(args.sortedBy); + const parseGraphQLConnectionArgumentsResult = - await parseGraphQLConnectionArguments({ + await parseGraphQLConnectionArgumentsWithSortedByAndWhere({ args, - parseCursor: (args) => + parseSortedByResult, + parseWhereResult, + parseCursor: /* c8 ignore start */ (args) => parseCursor({ ...args, organizationId: parent._id, }), + /* c8 ignore stop */ maximumLimit: MAXIMUM_FETCH_LIMIT, }); @@ -59,26 +69,43 @@ export const userTags: OrganizationResolvers["userTags"] = async ( const { parsedArgs } = parseGraphQLConnectionArgumentsResult; - const filter = getCommonGraphQLConnectionFilter({ + const objectListFilter = getUserTagGraphQLConnectionFilter({ cursor: parsedArgs.cursor, direction: parsedArgs.direction, + sortById: parsedArgs.sort.sortById, + nameStartsWith: parsedArgs.filter.nameStartsWith, }); - const sort = getCommonGraphQLConnectionSort({ + // don't use _id as a filter in while counting the documents + // _id is only used for pagination + const totalCountFilter = Object.fromEntries( + Object.entries(objectListFilter).filter(([key]) => key !== "_id"), + ); + + const sort = getUserTagGraphQLConnectionSort({ direction: parsedArgs.direction, + sortById: parsedArgs.sort.sortById, }); + // if there's no search input, we'll list all the root tag + // otherwise we'll also list the subtags matching the filter + const parentTagIdFilter = parsedArgs.filter.nameStartsWith + ? {} + : { parentTagId: null }; + const [objectList, totalCount] = await Promise.all([ OrganizationTagUser.find({ - ...filter, + ...objectListFilter, + ...parentTagIdFilter, organizationId: parent._id, - parentTagId: null, }) .sort(sort) .limit(parsedArgs.limit) .lean() .exec(), OrganizationTagUser.find({ + ...totalCountFilter, + ...parentTagIdFilter, organizationId: parent._id, }) .countDocuments() diff --git a/src/resolvers/Query/actionItemsByOrganization.ts b/src/resolvers/Query/actionItemsByOrganization.ts index 828cf58005e..ac7b10f7cd4 100644 --- a/src/resolvers/Query/actionItemsByOrganization.ts +++ b/src/resolvers/Query/actionItemsByOrganization.ts @@ -2,6 +2,7 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import type { InterfaceActionItem, InterfaceActionItemCategory, + InterfaceEventVolunteer, InterfaceUser, } from "../../models"; import { ActionItem } from "../../models"; @@ -24,7 +25,14 @@ export const actionItemsByOrganization: QueryResolvers["actionItemsByOrganizatio ...where, }) .populate("creator") - .populate("assignee") + .populate({ + path: "assignee", + populate: { + path: "user", + }, + }) + .populate("assigneeUser") + .populate("assigneeGroup") .populate("assigner") .populate("actionItemCategory") .populate("organization") @@ -46,10 +54,24 @@ export const actionItemsByOrganization: QueryResolvers["actionItemsByOrganizatio // Filter the action items based on assignee name if (args.where?.assigneeName) { + const assigneeName = args.where.assigneeName.toLowerCase(); filteredActionItems = filteredActionItems.filter((item) => { - const tempItem = item as InterfaceActionItem; - const assignee = tempItem.assignee as InterfaceUser; - return assignee.firstName.includes(args?.where?.assigneeName as string); + const assigneeType = item.assigneeType; + + if (assigneeType === "EventVolunteer") { + const assignee = item.assignee as InterfaceEventVolunteer; + const assigneeUser = assignee.user as InterfaceUser; + const name = + `${assigneeUser.firstName} ${assigneeUser.lastName}`.toLowerCase(); + + return name.includes(assigneeName); + } else if (assigneeType === "EventVolunteerGroup") { + return item.assigneeGroup.name.toLowerCase().includes(assigneeName); + } else if (assigneeType === "User") { + const name = + `${item.assigneeUser.firstName} ${item.assigneeUser.lastName}`.toLowerCase(); + return name.includes(assigneeName); + } }); } diff --git a/src/resolvers/Query/actionItemsByUser.ts b/src/resolvers/Query/actionItemsByUser.ts new file mode 100644 index 00000000000..43f1af3b769 --- /dev/null +++ b/src/resolvers/Query/actionItemsByUser.ts @@ -0,0 +1,109 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import type { + InterfaceActionItem, + InterfaceActionItemCategory, + InterfaceEvent, + InterfaceEventVolunteer, + InterfaceUser, +} from "../../models"; +import { ActionItem, EventVolunteer } from "../../models"; + +/** + * This query will fetch all action items for an organization from database. + * @param _parent- + * @param args - An object that contains `organizationId` which is the _id of the Organization. + * @returns An `actionItems` object that holds all action items for the Event. + */ +export const actionItemsByUser: QueryResolvers["actionItemsByUser"] = async ( + _parent, + args, +) => { + const volunteerObjects = await EventVolunteer.find({ + user: args.userId, + }) + .populate({ + path: "assignments", + populate: [ + { path: "creator" }, + { + path: "assignee", + populate: { path: "user" }, + }, + { path: "assigneeGroup" }, + { path: "assigner" }, + { path: "actionItemCategory" }, + { path: "organization" }, + { path: "event" }, + ], + }) + .populate("event") + .lean(); + + const userActionItems = await ActionItem.find({ + assigneeType: "User", + assigneeUser: args.userId, + organization: args.where?.orgId, + }) + .populate("creator") + .populate("assigner") + .populate("actionItemCategory") + .populate("organization") + .populate("assigneeUser") + .lean(); + + const actionItems: InterfaceActionItem[] = []; + volunteerObjects.forEach((volunteer) => { + const tempEvent = volunteer.event as InterfaceEvent; + if (tempEvent.organization._id.toString() === args.where?.orgId) + actionItems.push(...volunteer.assignments); + }); + + actionItems.push(...userActionItems); + + let filteredActionItems: InterfaceActionItem[] = actionItems; + + // filtering based on category name + if (args.where?.categoryName) { + const categoryName = args.where.categoryName.toLowerCase(); + filteredActionItems = filteredActionItems.filter((item) => { + const category = item.actionItemCategory as InterfaceActionItemCategory; + return category.name.toLowerCase().includes(categoryName); + }); + } + + // filtering based on assignee name + if (args.where?.assigneeName) { + const assigneeName = args.where.assigneeName.toLowerCase(); + + filteredActionItems = filteredActionItems.filter((item) => { + const assigneeType = item.assigneeType; + + if (assigneeType === "EventVolunteer") { + const assignee = item.assignee as InterfaceEventVolunteer; + const assigneeUser = assignee.user as InterfaceUser; + const name = + `${assigneeUser.firstName} ${assigneeUser.lastName}`.toLowerCase(); + + return name.includes(assigneeName); + } else if (assigneeType === "EventVolunteerGroup") { + return item.assigneeGroup.name.toLowerCase().includes(assigneeName); + } else if (assigneeType === "User") { + const name = + `${item.assigneeUser.firstName} ${item.assigneeUser.lastName}`.toLowerCase(); + return name.includes(assigneeName); + } + }); + } + + if (args.orderBy === "dueDate_DESC") { + filteredActionItems.sort((a, b) => { + return new Date(b.dueDate).getTime() - new Date(a.dueDate).getTime(); + }); + } else if (args.orderBy === "dueDate_ASC") { + filteredActionItems.sort((a, b) => { + return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime(); + }); + } + + return filteredActionItems as InterfaceActionItem[]; +}; diff --git a/src/resolvers/Query/chatById.ts b/src/resolvers/Query/chatById.ts new file mode 100644 index 00000000000..81fe769977e --- /dev/null +++ b/src/resolvers/Query/chatById.ts @@ -0,0 +1,26 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { errors } from "../../libraries"; +import { Chat } from "../../models"; +import { CHAT_NOT_FOUND_ERROR } from "../../constants"; +/** + * This query will fetch Chats by a specified id from the database. + * @param _parent- + * @param args - An object that contains `id` of the user. + * @returns An object `Chat`. + * If the `Chat` object is null then it throws `NotFoundError` error. + * @remarks You can learn about GraphQL `Resolvers` + * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. + */ +export const chatById: QueryResolvers["chatById"] = async (_parent, args) => { + const chat = await Chat.findById(args.id).lean(); + + if (!chat) { + throw new errors.NotFoundError( + CHAT_NOT_FOUND_ERROR.DESC, + CHAT_NOT_FOUND_ERROR.CODE, + CHAT_NOT_FOUND_ERROR.PARAM, + ); + } + + return chat; +}; diff --git a/src/resolvers/Query/chatsByUserId.ts b/src/resolvers/Query/chatsByUserId.ts new file mode 100644 index 00000000000..c99904ab9a8 --- /dev/null +++ b/src/resolvers/Query/chatsByUserId.ts @@ -0,0 +1,21 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { Chat } from "../../models"; +/** + * This query will fetch all the Chats for the current user from the database. + * @param _parent- + * @param args - An object that contains `id` of the user. + * @returns An object `chats` that contains all chats of the current user. + * If the `Chats` object is null then returns an empty array. + * @remarks You can learn about GraphQL `Resolvers` + * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. + */ +export const chatsByUserId: QueryResolvers["chatsByUserId"] = async ( + _parent, + args, +) => { + const chats = await Chat.find({ + users: args.id, + }).lean(); + + return chats || []; +}; diff --git a/src/resolvers/Query/checkAuth.ts b/src/resolvers/Query/checkAuth.ts index 09a0c6a7de8..1254fdeb599 100644 --- a/src/resolvers/Query/checkAuth.ts +++ b/src/resolvers/Query/checkAuth.ts @@ -37,11 +37,5 @@ export const checkAuth: QueryResolvers["checkAuth"] = async ( ); } - return { - ...currentUser, - image: currentUser.image - ? `${context.apiRootUrl}${currentUser.image}` - : null, - organizationsBlockedBy: [], - }; + return currentUser; }; diff --git a/src/resolvers/Query/directChatById.ts b/src/resolvers/Query/directChatById.ts deleted file mode 100644 index 00a4b9f8529..00000000000 --- a/src/resolvers/Query/directChatById.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import { errors } from "../../libraries"; -import { DirectChat } from "../../models"; -import { CHAT_NOT_FOUND_ERROR } from "../../constants"; - -/** - * This query will fetch all messages for a certain direct chat for the user from database. - * @param _parent- - * @param args - An object that contains `id` of the direct chat. - * @returns A `directChatsMessages` object that holds all of the messages from the specified direct chat. - * If the `directChatsMessages` object is null then it throws `NotFoundError` error. - * @remarks You can learn about GraphQL `Resolvers` - * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. - */ - -export const directChatById: QueryResolvers["directChatById"] = async ( - _parent, - args, -) => { - const directChat = await DirectChat.findById(args.id).lean(); - - if (!directChat) { - throw new errors.NotFoundError( - CHAT_NOT_FOUND_ERROR.DESC, - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - return directChat; -}; diff --git a/src/resolvers/Query/directChatsByUserID.ts b/src/resolvers/Query/directChatsByUserID.ts deleted file mode 100644 index efb34e93dd7..00000000000 --- a/src/resolvers/Query/directChatsByUserID.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import { errors } from "../../libraries"; -import { DirectChat } from "../../models"; -/** - * This query will fetch all the Direct chats for the current user from the database. - * @param _parent- - * @param args - An object that contains `id` of the user. - * @returns An object `directChats` that contains all direct chats of the current user. - * If the `directChats` object is null then it throws `NotFoundError` error. - * @remarks You can learn about GraphQL `Resolvers` - * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. - */ -export const directChatsByUserID: QueryResolvers["directChatsByUserID"] = - async (_parent, args) => { - const directChats = await DirectChat.find({ - users: args.id, - }).lean(); - - if (directChats.length === 0) { - throw new errors.NotFoundError( - "DirectChats not found", - "directChats.notFound", - "directChats", - ); - } - - return directChats; - }; diff --git a/src/resolvers/Query/directChatsMessagesByChatID.ts b/src/resolvers/Query/directChatsMessagesByChatID.ts deleted file mode 100644 index 173eada6e5a..00000000000 --- a/src/resolvers/Query/directChatsMessagesByChatID.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import { errors } from "../../libraries"; -import { DirectChatMessage } from "../../models"; -import { CHAT_NOT_FOUND_ERROR } from "../../constants"; - -/** - * This query will fetch all messages for a certain direct chat for the user from database. - * @param _parent- - * @param args - An object that contains `id` of the direct chat. - * @returns A `directChatsMessages` object that holds all of the messages from the specified direct chat. - * If the `directChatsMessages` object is null then it throws `NotFoundError` error. - * @remarks You can learn about GraphQL `Resolvers` - * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. - */ - -export const directChatsMessagesByChatID: QueryResolvers["directChatsMessagesByChatID"] = - async (_parent, args) => { - const directChatsMessages = await DirectChatMessage.find({ - directChatMessageBelongsTo: args.id, - }).lean(); - - if (directChatsMessages.length === 0) { - throw new errors.NotFoundError( - CHAT_NOT_FOUND_ERROR.DESC, - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - return directChatsMessages; - }; diff --git a/src/resolvers/Query/eventVolunteersByEvent.ts b/src/resolvers/Query/eventVolunteersByEvent.ts deleted file mode 100644 index e982f58e189..00000000000 --- a/src/resolvers/Query/eventVolunteersByEvent.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import { EventVolunteer } from "../../models"; -/** - * This query will fetch all events volunteers for the given eventId from database. - * @param _parent- - * @param args - An object that contains `id` of the Event. - * @returns An object that holds all Event Volunteers for the given Event - */ -export const eventVolunteersByEvent: QueryResolvers["eventVolunteersByEvent"] = - async (_parent, args) => { - const eventId = args.id; - - const volunteers = EventVolunteer.find({ - eventId: eventId, - }) - .populate("userId", "-password") - .lean(); - - return volunteers; - }; diff --git a/src/resolvers/Query/eventsAttendedByUser.ts b/src/resolvers/Query/eventsAttendedByUser.ts new file mode 100644 index 00000000000..202a962707c --- /dev/null +++ b/src/resolvers/Query/eventsAttendedByUser.ts @@ -0,0 +1,28 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { Event } from "../../models"; +import { getSort } from "./helperFunctions/getSort"; + +/** + * This query will fetch all the events for which user attended from the database. + * @param _parent- + * @param args - An object that contains `id` of the user and `orderBy`. + * @returns An object that contains the Event data. + * @remarks The query function uses `getSort()` function to sort the data in specified. + */ +export const eventsAttendedByUser: QueryResolvers["eventsAttendedByUser"] = + async (_parent, args) => { + const sort = getSort(args.orderBy); + + return await Event.find({ + registrants: { + $elemMatch: { + userId: args.id, + status: "ACTIVE", + }, + }, + }) + .sort(sort) + .populate("creatorId", "-password") + .populate("admins", "-password") + .lean(); + }; diff --git a/src/resolvers/Query/eventsByOrganizationConnection.ts b/src/resolvers/Query/eventsByOrganizationConnection.ts index e184a2bc471..0991d0a8afe 100644 --- a/src/resolvers/Query/eventsByOrganizationConnection.ts +++ b/src/resolvers/Query/eventsByOrganizationConnection.ts @@ -4,6 +4,7 @@ import { Event } from "../../models"; import { getSort } from "./helperFunctions/getSort"; import { getWhere } from "./helperFunctions/getWhere"; import { createRecurringEventInstancesDuringQuery } from "../../helpers/event/createEventHelpers"; + /** * Retrieves events for a specific organization based on the provided query parameters. * @@ -26,10 +27,19 @@ export const eventsByOrganizationConnection: QueryResolvers["eventsByOrganizatio // get the where and sort let where = getWhere(args.where); const sort = getSort(args.orderBy); - + const currentDate = new Date(); where = { ...where, isBaseRecurringEvent: false, + ...(args.upcomingOnly && { + $or: [ + { endDate: { $gt: currentDate } }, // Future dates + { + endDate: { $eq: currentDate.toISOString().split("T")[0] }, // Events today + endTime: { $gt: currentDate }, // But start time is after current time + }, + ], + }), }; // find all the events according to the requirements @@ -39,6 +49,13 @@ export const eventsByOrganizationConnection: QueryResolvers["eventsByOrganizatio .skip(args.skip ?? 0) .populate("creatorId", "-password") .populate("admins", "-password") + .populate("volunteerGroups") + .populate({ + path: "volunteers", + populate: { + path: "user", + }, + }) .lean(); return events; diff --git a/src/resolvers/Query/getEventVolunteerGroups.ts b/src/resolvers/Query/getEventVolunteerGroups.ts index bb5e7d558ec..f4f6913d3ed 100644 --- a/src/resolvers/Query/getEventVolunteerGroups.ts +++ b/src/resolvers/Query/getEventVolunteerGroups.ts @@ -1,4 +1,10 @@ -import { EventVolunteerGroup } from "../../models"; +import type { + InterfaceEvent, + InterfaceEventVolunteer, + InterfaceEventVolunteerGroup, + InterfaceUser, +} from "../../models"; +import { EventVolunteer, EventVolunteerGroup } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { getWhere } from "./helperFunctions/getWhere"; /** @@ -9,14 +15,111 @@ import { getWhere } from "./helperFunctions/getWhere"; */ export const getEventVolunteerGroups: QueryResolvers["getEventVolunteerGroups"] = async (_parent, args) => { - const where = getWhere(args.where); - const eventVolunteerGroups = await EventVolunteerGroup.find({ - ...where, - }) - .populate("eventId") - .populate("creatorId") - .populate("leaderId") - .populate("volunteers"); + const { eventId, leaderName, userId, orgId } = args.where; + let eventVolunteerGroups: InterfaceEventVolunteerGroup[] = []; + if (eventId) { + const where = getWhere({ name_contains: args.where.name_contains }); + eventVolunteerGroups = await EventVolunteerGroup.find({ + event: eventId, + ...where, + }) + .populate("event") + .populate("creator") + .populate("leader") + .populate({ + path: "volunteers", + populate: { + path: "user", + }, + }) + .populate({ + path: "assignments", + populate: { + path: "actionItemCategory", + }, + }) + .lean(); + } else if (userId && orgId) { + const volunteerProfiles = (await EventVolunteer.find({ + user: userId, + }) + .populate({ + path: "groups", + populate: [ + { + path: "event", + }, + { + path: "creator", + }, + { + path: "leader", + }, + { + path: "volunteers", + populate: { + path: "user", + }, + }, + { + path: "assignments", + populate: { + path: "actionItemCategory", + }, + }, + ], + }) + .populate("event") + .lean()) as InterfaceEventVolunteer[]; + volunteerProfiles.forEach((volunteer) => { + const tempEvent = volunteer.event as InterfaceEvent; + if (tempEvent.organization.toString() == orgId) + eventVolunteerGroups.push(...volunteer.groups); + }); + } - return eventVolunteerGroups; + let filteredEventVolunteerGroups: InterfaceEventVolunteerGroup[] = + eventVolunteerGroups; + + if (leaderName) { + const tempName = leaderName.toLowerCase(); + filteredEventVolunteerGroups = filteredEventVolunteerGroups.filter( + (group) => { + const tempGroup = group as InterfaceEventVolunteerGroup; + const tempLeader = tempGroup.leader as InterfaceUser; + const { firstName, lastName } = tempLeader; + const name = `${firstName} ${lastName}`.toLowerCase(); + return name.includes(tempName); + }, + ); + } + + const sortConfigs = { + /* c8 ignore start */ + volunteers_ASC: ( + a: InterfaceEventVolunteerGroup, + b: InterfaceEventVolunteerGroup, + ): number => a.volunteers.length - b.volunteers.length, + /* c8 ignore stop */ + volunteers_DESC: ( + a: InterfaceEventVolunteerGroup, + b: InterfaceEventVolunteerGroup, + ): number => b.volunteers.length - a.volunteers.length, + assignments_ASC: ( + a: InterfaceEventVolunteerGroup, + b: InterfaceEventVolunteerGroup, + ): number => a.assignments.length - b.assignments.length, + assignments_DESC: ( + a: InterfaceEventVolunteerGroup, + b: InterfaceEventVolunteerGroup, + ): number => b.assignments.length - a.assignments.length, + }; + + if (args.orderBy && args.orderBy in sortConfigs) { + filteredEventVolunteerGroups.sort( + sortConfigs[args.orderBy as keyof typeof sortConfigs], + ); + } + + return filteredEventVolunteerGroups; }; diff --git a/src/resolvers/Query/getEventVolunteers.ts b/src/resolvers/Query/getEventVolunteers.ts new file mode 100644 index 00000000000..ccb461f70c7 --- /dev/null +++ b/src/resolvers/Query/getEventVolunteers.ts @@ -0,0 +1,61 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import type { InterfaceEventVolunteer, InterfaceUser } from "../../models"; +import { EventVolunteer } from "../../models"; +import { getSort } from "./helperFunctions/getSort"; +import { getWhere } from "./helperFunctions/getWhere"; + +/** + * This query will fetch all events volunteers for the given eventId from database. + * @param _parent- + * @param args - An object that contains `id` of the Event. + * @returns An object that holds all Event Volunteers for the given Event + */ +export const getEventVolunteers: QueryResolvers["getEventVolunteers"] = async ( + _parent, + args, +) => { + const sort = getSort(args.orderBy); + const { + id, + name_contains: nameContains, + hasAccepted, + eventId, + groupId, + } = args.where; + const where = getWhere({ id, hasAccepted }); + + const volunteers = await EventVolunteer.find({ + event: eventId, + ...(groupId && { + groups: { + $in: groupId, + }, + }), + ...where, + }) + .populate("user", "-password") + .populate("event") + .populate("groups") + .populate({ + path: "assignments", + populate: { + path: "actionItemCategory", + }, + }) + .sort(sort) + .lean(); + + let filteredVolunteers: InterfaceEventVolunteer[] = volunteers; + + if (nameContains) { + filteredVolunteers = filteredVolunteers.filter((volunteer) => { + const tempVolunteer = volunteer as InterfaceEventVolunteer; + const tempUser = tempVolunteer.user as InterfaceUser; + const { firstName, lastName } = tempUser; + const name = `${firstName} ${lastName}`.toLowerCase(); + return name.includes(nameContains.toLowerCase()); + }); + } + + return filteredVolunteers; +}; diff --git a/src/resolvers/Query/getRecurringEvents.ts b/src/resolvers/Query/getRecurringEvents.ts new file mode 100644 index 00000000000..e36ad7bb77d --- /dev/null +++ b/src/resolvers/Query/getRecurringEvents.ts @@ -0,0 +1,25 @@ +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { Event } from "../../models"; +import type { InterfaceEvent } from "../../models/Event"; +/** + * This query will fetch all the events with the same BaseRecurringEventId from the database. + * @param _parent - + * @param args - An object that contains `baseRecurringEventId` of the base recurring event. + * @returns An array of `Event` objects that are instances of the base recurring event. + */ + +export const getRecurringEvents: QueryResolvers["getRecurringEvents"] = async ( + _parent, + args, +) => { + try { + const recurringEvents = await Event.find({ + baseRecurringEventId: args.baseRecurringEventId, + }).lean(); + + return recurringEvents as InterfaceEvent[]; + } catch (error) { + console.error("Error fetching recurring events:", error); + throw error; + } +}; diff --git a/src/resolvers/Query/getUserTagAncestors.ts b/src/resolvers/Query/getUserTagAncestors.ts deleted file mode 100644 index cf516eb43eb..00000000000 --- a/src/resolvers/Query/getUserTagAncestors.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { InterfaceOrganizationTagUser } from "../../models"; -import { OrganizationTagUser } from "../../models"; -import { errors, requestContext } from "../../libraries"; -import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import { TAG_NOT_FOUND } from "../../constants"; - -/** - * Retrieves the ancestor tags of a given user tag. - * - * This function fetches the ancestor tags of a specific user tag from the database. If the user tag - * is not found, it throws an error indicating that the item does not exist. - * - * @param _parent - This parameter is not used in this resolver function. - * @param args - The arguments provided by the GraphQL query, including the ID of the given user tag. - * - * @returns The ancestor tags of the user tag. - */ - -export const getUserTagAncestors: QueryResolvers["getUserTagAncestors"] = - async (_parent, args) => { - let currentTag = await OrganizationTagUser.findById(args.id).lean(); - - if (!currentTag) { - throw new errors.NotFoundError( - requestContext.translate(TAG_NOT_FOUND.MESSAGE), - TAG_NOT_FOUND.CODE, - TAG_NOT_FOUND.PARAM, - ); - } - - const tagAncestors = [currentTag]; - - while (currentTag?.parentTagId) { - const currentParent = (await OrganizationTagUser.findById( - currentTag.parentTagId, - ).lean()) as InterfaceOrganizationTagUser | null; - - if (currentParent) { - tagAncestors.push(currentParent); - currentTag = currentParent; - } - } - - return tagAncestors.reverse(); - }; diff --git a/src/resolvers/Query/getVolunteerMembership.ts b/src/resolvers/Query/getVolunteerMembership.ts new file mode 100644 index 00000000000..f9dc8f18312 --- /dev/null +++ b/src/resolvers/Query/getVolunteerMembership.ts @@ -0,0 +1,129 @@ +import type { + InputMaybe, + QueryResolvers, + VolunteerMembershipOrderByInput, +} from "../../types/generatedGraphQLTypes"; +import type { InterfaceVolunteerMembership } from "../../models"; +import { EventVolunteer, VolunteerMembership } from "../../models"; +import { getSort } from "./helperFunctions/getSort"; + +/** + * Helper function to fetch volunteer memberships by userId + */ +const getVolunteerMembershipsByUserId = async ( + userId: string, + orderBy: InputMaybe | undefined, + status?: string, +): Promise => { + const sort = getSort(orderBy); + const volunteerInstance = await EventVolunteer.find({ user: userId }).lean(); + const volunteerIds = volunteerInstance.map((volunteer) => volunteer._id); + + return await VolunteerMembership.find({ + volunteer: { $in: volunteerIds }, + ...(status && { status }), + }) + .sort(sort) + .populate("event") + .populate("group") + .populate({ + path: "volunteer", + populate: { + path: "user", + }, + }) + .lean(); +}; + +/** + * Helper function to fetch volunteer memberships by eventId + */ +const getVolunteerMembershipsByEventId = async ( + eventId: string, + orderBy: InputMaybe | undefined, + status?: string, + group?: string, +): Promise => { + const sort = getSort(orderBy); + + return await VolunteerMembership.find({ + event: eventId, + ...(status && { status }), + ...(group && { group: group }), + }) + .sort(sort) + .populate("event") + .populate("group") + .populate({ + path: "volunteer", + populate: { + path: "user", + }, + }) + .lean(); +}; + +/** + * Helper function to filter memberships based on various criteria + */ +const filterMemberships = ( + memberships: InterfaceVolunteerMembership[], + filter?: string, + eventTitle?: string, + userName?: string, +): InterfaceVolunteerMembership[] => { + return memberships.filter((membership) => { + const filterCondition = filter + ? filter === "group" + ? !!membership.group + : !membership.group + : true; + + const eventTitleCondition = eventTitle + ? membership.event.title.includes(eventTitle) + : true; + + const userNameCondition = userName + ? ( + membership.volunteer.user.firstName + + membership.volunteer.user.lastName + ).includes(userName) + : true; + + return filterCondition && eventTitleCondition && userNameCondition; + }); +}; + +export const getVolunteerMembership: QueryResolvers["getVolunteerMembership"] = + async (_parent, args) => { + const { status, userId, filter, eventTitle, eventId, userName, groupId } = + args.where; + + let volunteerMemberships: InterfaceVolunteerMembership[] = []; + + if (userId) { + volunteerMemberships = await getVolunteerMembershipsByUserId( + userId, + args.orderBy, + status ?? undefined, + ); + } else if (eventId) { + volunteerMemberships = await getVolunteerMembershipsByEventId( + eventId, + args.orderBy, + status ?? undefined, + groupId ?? undefined, + ); + } + + if (filter || eventTitle || userName) { + return filterMemberships( + volunteerMemberships, + filter ?? undefined, + eventTitle ?? undefined, + userName ?? undefined, + ); + } + + return volunteerMemberships; + }; diff --git a/src/resolvers/Query/getVolunteerRanks.ts b/src/resolvers/Query/getVolunteerRanks.ts new file mode 100644 index 00000000000..a117652f62f --- /dev/null +++ b/src/resolvers/Query/getVolunteerRanks.ts @@ -0,0 +1,146 @@ +import { startOfWeek, startOfMonth, startOfYear, endOfDay } from "date-fns"; +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import type { InterfaceEvent, InterfaceUser } from "../../models"; +import { Event, EventVolunteer } from "../../models"; + +/** + * This query will fetch volunteer ranks based on the provided time frame (allTime, weekly, monthly, yearly), + * and it will filter the results based on an array of volunteer IDs. + * @param _parent - parent of the current request + * @param args - An object that contains where object for volunteer ranks. + * + * @returns An array of `VolunteerRank` object. + */ +export const getVolunteerRanks: QueryResolvers["getVolunteerRanks"] = async ( + _parent, + args, +) => { + const { orgId } = args; + const { timeFrame, orderBy, nameContains, limit } = args.where; + + const volunteerIds: string[] = []; + const events = (await Event.find({ + organization: orgId, + }).lean()) as InterfaceEvent[]; + + // Get all volunteer IDs from the events + events.forEach((event) => { + volunteerIds.push( + ...event.volunteers.map((volunteer) => volunteer.toString()), + ); + }); + + // Fetch all volunteers + const volunteers = await EventVolunteer.find({ + _id: { $in: volunteerIds }, + }) + .populate("user") + .lean(); + + const now = new Date(); + let startDate: Date | null = null; + let endDate: Date | null = null; + + // Determine the date range based on the timeframe + switch (timeFrame) { + case "weekly": + startDate = startOfWeek(now); + endDate = endOfDay(now); + break; + case "monthly": + startDate = startOfMonth(now); + endDate = endOfDay(now); + break; + case "yearly": + startDate = startOfYear(now); + endDate = endOfDay(now); + break; + case "allTime": + default: + startDate = null; // No filtering for "allTime" + endDate = null; + break; + } + + // Accumulate total hours per user + const userHoursMap = new Map< + string, + { hoursVolunteered: number; user: InterfaceUser } + >(); + + volunteers.forEach((volunteer) => { + const userId = volunteer.user._id.toString(); + let totalHours = 0; + + // Filter hoursHistory based on the time frame + if (startDate && endDate) { + totalHours = volunteer.hoursHistory.reduce((sum, record) => { + const recordDate = new Date(record.date); + // Check if the record date is within the specified range + if (recordDate >= startDate && recordDate <= endDate) { + return sum + record.hours; + } + return sum; + }, 0); + } else { + // If "allTime", use hoursVolunteered + totalHours = volunteer.hoursVolunteered; + } + + // Accumulate hours for each user + /* c8 ignore start */ + const existingRecord = userHoursMap.get(userId); + if (existingRecord) { + existingRecord.hoursVolunteered += totalHours; + } else { + userHoursMap.set(userId, { + hoursVolunteered: totalHours, + user: volunteer.user, + }); + } + /* c8 ignore stop */ + }); + + // Convert the accumulated map to an array + const volunteerRanks = Array.from(userHoursMap.values()); + + volunteerRanks.sort((a, b) => b.hoursVolunteered - a.hoursVolunteered); + + // Assign ranks, accounting for ties + const rankedVolunteers = []; + let currentRank = 1; + let lastHours = -1; + + for (const volunteer of volunteerRanks) { + if (volunteer.hoursVolunteered !== lastHours) { + currentRank = rankedVolunteers.length + 1; // New rank + } + + rankedVolunteers.push({ + rank: currentRank, + user: volunteer.user, + hoursVolunteered: volunteer.hoursVolunteered, + }); + + lastHours = volunteer.hoursVolunteered; // Update lastHours + } + + // Sort the ranked volunteers based on the orderBy field + + if (orderBy === "hours_ASC") { + rankedVolunteers.sort((a, b) => a.hoursVolunteered - b.hoursVolunteered); + } else if (orderBy === "hours_DESC") { + rankedVolunteers.sort((a, b) => b.hoursVolunteered - a.hoursVolunteered); + } + + // Filter by name + if (nameContains) { + return rankedVolunteers.filter((volunteer) => { + const fullName = + `${volunteer.user.firstName} ${volunteer.user.lastName}`.toLowerCase(); + return fullName.includes(nameContains.toLowerCase()); + }); + } + + return limit ? rankedVolunteers.slice(0, limit) : rankedVolunteers; +}; diff --git a/src/resolvers/Query/groupChatById.ts b/src/resolvers/Query/groupChatById.ts deleted file mode 100644 index 43a00ce65d4..00000000000 --- a/src/resolvers/Query/groupChatById.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import { errors } from "../../libraries"; -import { GroupChat } from "../../models"; -import { CHAT_NOT_FOUND_ERROR } from "../../constants"; - -/** - * This query will fetch all messages for a certain direct chat for the user from database. - * @param _parent- - * @param args - An object that contains `id` of the direct chat. - * @returns A `directChatsMessages` object that holds all of the messages from the specified direct chat. - * If the `directChatsMessages` object is null then it throws `NotFoundError` error. - * @remarks You can learn about GraphQL `Resolvers` - * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. - */ - -export const groupChatById: QueryResolvers["groupChatById"] = async ( - _parent, - args, -) => { - const directChat = await GroupChat.findById(args.id).lean(); - - if (!directChat) { - throw new errors.NotFoundError( - CHAT_NOT_FOUND_ERROR.DESC, - CHAT_NOT_FOUND_ERROR.CODE, - CHAT_NOT_FOUND_ERROR.PARAM, - ); - } - - return directChat; -}; diff --git a/src/resolvers/Query/groupChatsByUserId.ts b/src/resolvers/Query/groupChatsByUserId.ts deleted file mode 100644 index a3b3310d3e5..00000000000 --- a/src/resolvers/Query/groupChatsByUserId.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import { errors } from "../../libraries"; -import { GroupChat } from "../../models"; -/** - * This query will fetch all the Direct chats for the current user from the database. - * @param _parent- - * @param args - An object that contains `id` of the user. - * @returns An object `GroupChat` that contains all direct chats of the current user. - * If the `directChats` object is null then it throws `NotFoundError` error. - * @remarks You can learn about GraphQL `Resolvers` - * {@link https://www.apollographql.com/docs/apollo-server/data/resolvers/ | here}. - */ -export const groupChatsByUserId: QueryResolvers["groupChatsByUserId"] = async ( - _parent, - args, -) => { - const groupChats = await GroupChat.find({ - users: args.id, - }).lean(); - - if (groupChats.length === 0) { - throw new errors.NotFoundError( - "Group Chats not found", - "groupChats.notFound", - "groupChats", - ); - } - - return groupChats; -}; diff --git a/src/resolvers/Query/helperFunctions/getSort.ts b/src/resolvers/Query/helperFunctions/getSort.ts index d3f68a704c5..a00bde9a217 100644 --- a/src/resolvers/Query/helperFunctions/getSort.ts +++ b/src/resolvers/Query/helperFunctions/getSort.ts @@ -10,6 +10,8 @@ import type { CampaignOrderByInput, FundOrderByInput, ActionItemsOrderByInput, + EventVolunteersOrderByInput, + VolunteerMembershipOrderByInput, } from "../../../types/generatedGraphQLTypes"; export const getSort = ( @@ -24,6 +26,8 @@ export const getSort = ( | CampaignOrderByInput | PledgeOrderByInput | ActionItemsOrderByInput + | EventVolunteersOrderByInput + | VolunteerMembershipOrderByInput > | undefined, ): @@ -335,6 +339,18 @@ export const getSort = ( }; break; + case "hoursVolunteered_ASC": + sortPayload = { + hoursVolunteered: 1, + }; + break; + + case "hoursVolunteered_DESC": + sortPayload = { + hoursVolunteered: -1, + }; + break; + default: break; } diff --git a/src/resolvers/Query/helperFunctions/getWhere.ts b/src/resolvers/Query/helperFunctions/getWhere.ts index e2288ee6cb3..56207568e46 100644 --- a/src/resolvers/Query/helperFunctions/getWhere.ts +++ b/src/resolvers/Query/helperFunctions/getWhere.ts @@ -13,6 +13,7 @@ import type { CampaignWhereInput, PledgeWhereInput, ActionItemCategoryWhereInput, + EventVolunteerWhereInput, } from "../../../types/generatedGraphQLTypes"; /** @@ -43,7 +44,8 @@ export const getWhere = ( CampaignWhereInput & FundWhereInput & PledgeWhereInput & - VenueWhereInput + VenueWhereInput & + EventVolunteerWhereInput > > | undefined, @@ -764,21 +766,18 @@ export const getWhere = ( }; } - // Returns objects where volunteerId is present in volunteers list - if (where.volunteerId) { + // Returns object with provided is_disabled condition + if (where.is_disabled !== undefined) { wherePayload = { ...wherePayload, - volunteers: { - $in: [where.volunteerId], - }, + isDisabled: where.is_disabled, }; } - // Returns object with provided is_disabled condition - if (where.is_disabled !== undefined) { + if (where.hasAccepted !== undefined) { wherePayload = { ...wherePayload, - isDisabled: where.is_disabled, + hasAccepted: where.hasAccepted, }; } diff --git a/src/resolvers/Query/index.ts b/src/resolvers/Query/index.ts index fc878303d8f..534dec83ce8 100644 --- a/src/resolvers/Query/index.ts +++ b/src/resolvers/Query/index.ts @@ -2,6 +2,7 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { isSampleOrganization } from "../Query/organizationIsSample"; import { actionItemCategoriesByOrganization } from "./actionItemCategoriesByOrganization"; import { actionItemsByEvent } from "./actionItemsByEvent"; +import { actionItemsByUser } from "./actionItemsByUser"; import { actionItemsByOrganization } from "./actionItemsByOrganization"; import { advertisementsConnection } from "./advertisementsConnection"; import { agendaCategory } from "./agendaCategory"; @@ -13,14 +14,12 @@ import { getAgendaSection } from "./getAgendaSection"; import { checkAuth } from "./checkAuth"; import { customDataByOrganization } from "./customDataByOrganization"; import { customFieldsByOrganization } from "./customFieldsByOrganization"; -import { directChatsByUserID } from "./directChatsByUserID"; -import { directChatsMessagesByChatID } from "./directChatsMessagesByChatID"; -import { directChatById } from "./directChatById"; -import { groupChatById } from "./groupChatById"; -import { groupChatsByUserId } from "./groupChatsByUserId"; +import { chatById } from "./chatById"; +import { chatsByUserId } from "./chatsByUserId"; import { event } from "./event"; import { eventsByOrganization } from "./eventsByOrganization"; import { eventsByOrganizationConnection } from "./eventsByOrganizationConnection"; +import { getEventVolunteers } from "./getEventVolunteers"; import { getEventVolunteerGroups } from "./getEventVolunteerGroups"; import { fundsByOrganization } from "./fundsByOrganization"; import { getAllAgendaItems } from "./getAllAgendaItems"; @@ -35,7 +34,6 @@ import { getPledgesByUserId } from "./getPledgesByUserId"; import { getPlugins } from "./getPlugins"; import { getlanguage } from "./getlanguage"; import { getUserTag } from "./getUserTag"; -import { getUserTagAncestors } from "./getUserTagAncestors"; import { me } from "./me"; import { myLanguage } from "./myLanguage"; import { organizations } from "./organizations"; @@ -53,8 +51,14 @@ import { getEventAttendeesByEventId } from "./getEventAttendeesByEventId"; import { getVenueByOrgId } from "./getVenueByOrgId"; import { getAllNotesForAgendaItem } from "./getAllNotesForAgendaItem"; import { getNoteById } from "./getNoteById"; +import { eventsAttendedByUser } from "./eventsAttendedByUser"; +import { getRecurringEvents } from "./getRecurringEvents"; +import { getVolunteerMembership } from "./getVolunteerMembership"; +import { getVolunteerRanks } from "./getVolunteerRanks"; + export const Query: QueryResolvers = { actionItemsByEvent, + actionItemsByUser, agendaCategory, getAgendaItem, getAgendaSection, @@ -68,11 +72,8 @@ export const Query: QueryResolvers = { getCommunityData, customFieldsByOrganization, customDataByOrganization, - directChatsByUserID, - directChatsMessagesByChatID, - directChatById, - groupChatById, - groupChatsByUserId, + chatById, + chatsByUserId, event, eventsByOrganization, eventsByOrganizationConnection, @@ -81,13 +82,14 @@ export const Query: QueryResolvers = { getDonationByOrgId, getDonationByOrgIdConnection, getEventInvitesByUserId, + getEventVolunteers, getEventVolunteerGroups, getAllNotesForAgendaItem, getNoteById, getlanguage, getPlugins, + getRecurringEvents, getUserTag, - getUserTagAncestors, isSampleOrganization, me, myLanguage, @@ -108,4 +110,7 @@ export const Query: QueryResolvers = { getEventAttendee, getEventAttendeesByEventId, getVenueByOrgId, + eventsAttendedByUser, + getVolunteerMembership, + getVolunteerRanks, }; diff --git a/src/resolvers/Query/organizationsMemberConnection.ts b/src/resolvers/Query/organizationsMemberConnection.ts index 76a85c4d887..7ccc1ea8052 100644 --- a/src/resolvers/Query/organizationsMemberConnection.ts +++ b/src/resolvers/Query/organizationsMemberConnection.ts @@ -17,7 +17,7 @@ import { getWhere } from "./helperFunctions/getWhere"; * learn more about Connection {@link https://relay.dev/graphql/connections.htm | here}. */ export const organizationsMemberConnection: QueryResolvers["organizationsMemberConnection"] = - async (_parent, args, context) => { + async (_parent, args) => { const where = getWhere(args.where); const sort = getSort(args.orderBy); @@ -111,6 +111,7 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC if (paginateOptions.pagination) { users = usersModel.docs.map((user) => ({ _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: { city: user.address?.city, @@ -129,7 +130,7 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC employmentStatus: user.employmentStatus, firstName: user.firstName, gender: user.gender, - image: user.image ? `${context.apiRootUrl}${user.image}` : null, + image: user.image ?? null, joinedOrganizations: user.joinedOrganizations, lastName: user.lastName, maritalStatus: user.maritalStatus, @@ -140,10 +141,12 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, })); } else { users = usersModel.docs.map((user) => ({ _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: { city: user.address?.city, @@ -162,7 +165,7 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC employmentStatus: user.employmentStatus, firstName: user.firstName, gender: user.gender, - image: user.image ? `${context.apiRootUrl}${user.image}` : null, + image: user.image ?? null, joinedOrganizations: user.joinedOrganizations, lastName: user.lastName, maritalStatus: user.maritalStatus, @@ -173,6 +176,7 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, })); } diff --git a/src/resolvers/Query/post.ts b/src/resolvers/Query/post.ts index 258c591eb95..9eb82f7453c 100644 --- a/src/resolvers/Query/post.ts +++ b/src/resolvers/Query/post.ts @@ -8,10 +8,10 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; * @param args - An object that contains `id` of the Post. * @returns An object `post`. If the `appLanguageCode` field not found then it throws a `NotFoundError` error. */ -export const post: QueryResolvers["post"] = async (_parent, args, context) => { +export const post: QueryResolvers["post"] = async (_parent, args) => { const post = await Post.findOne({ _id: args.id }) .populate("organization") - .populate("likedBy") + .populate(["likedBy", "file"]) .lean(); if (!post) { @@ -21,12 +21,6 @@ export const post: QueryResolvers["post"] = async (_parent, args, context) => { POST_NOT_FOUND_ERROR.PARAM, ); } - post.imageUrl = post.imageUrl - ? `${context.apiRootUrl}${post.imageUrl}` - : null; - post.videoUrl = post.videoUrl - ? `${context.apiRootUrl}${post.videoUrl}` - : null; return post; }; diff --git a/src/resolvers/Query/user.ts b/src/resolvers/Query/user.ts index c0716be0c3f..dc6233a0116 100644 --- a/src/resolvers/Query/user.ts +++ b/src/resolvers/Query/user.ts @@ -43,7 +43,6 @@ export const user: QueryResolvers["user"] = async (_parent, args, context) => { return { user: { ...user, - image: user?.image ? `${context.apiRootUrl}${user.image}` : null, organizationsBlockedBy: [], }, appUserProfile: userAppProfile, diff --git a/src/resolvers/Subscription/directMessageChat.ts b/src/resolvers/Subscription/directMessageChat.ts deleted file mode 100644 index 38af5cbd3af..00000000000 --- a/src/resolvers/Subscription/directMessageChat.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { withFilter } from "graphql-subscriptions"; -import type { SubscriptionResolvers } from "../../types/generatedGraphQLTypes"; - -const CHAT_CHANNEL = "CHAT_CHANNEL"; -/** - * This property contained a `subscribe` field, which is used to subscribe - * the user to get updates for the `CHAT_CHANNEL` event. - * @remarks To control updates on a per-client basis, the function uses the `withFilter` - * method imported from `apollo-server-express` module. - * You can learn about `subscription` {@link https://www.apollographql.com/docs/apollo-server/data/subscriptions/ | here }. - */ -export const directMessageChat: SubscriptionResolvers["directMessageChat"] = { - // @ts-expect-error-ts-ignore - subscribe: withFilter( - (_parent, _args, context) => context.pubsub.asyncIterator(CHAT_CHANNEL), - - (payload) => payload?.directMessageChat, - ), -}; diff --git a/src/resolvers/Subscription/index.ts b/src/resolvers/Subscription/index.ts index c981720fcbe..f2f19e4836a 100644 --- a/src/resolvers/Subscription/index.ts +++ b/src/resolvers/Subscription/index.ts @@ -1,11 +1,7 @@ import type { SubscriptionResolvers } from "../../types/generatedGraphQLTypes"; -import { directMessageChat } from "./directMessageChat"; -import { messageSentToDirectChat } from "./messageSentToDirectChat"; -import { messageSentToGroupChat } from "./messageSentToGroupChat"; +import { messageSentToChat } from "./messageSentToChat"; import { onPluginUpdate } from "./onPluginUpdate"; export const Subscription: SubscriptionResolvers = { - directMessageChat, - messageSentToDirectChat, - messageSentToGroupChat, + messageSentToChat, onPluginUpdate, }; diff --git a/src/resolvers/Subscription/messageSentToChat.ts b/src/resolvers/Subscription/messageSentToChat.ts new file mode 100644 index 00000000000..936fd104de5 --- /dev/null +++ b/src/resolvers/Subscription/messageSentToChat.ts @@ -0,0 +1,43 @@ +import { withFilter } from "graphql-subscriptions"; +import type { SubscriptionResolvers } from "../../types/generatedGraphQLTypes"; +import { Chat } from "../../models"; + +const MESSAGE_SENT_TO_CHAT = "MESSAGE_SENT_TO_CHAT"; + +export const filterFunction = async function ( + payload: { messageSentToChat: { chatMessageBelongsTo: string } }, + variables: { userId: string }, +): Promise { + const currentUserId = variables.userId.toString(); + const chatId = payload.messageSentToChat.chatMessageBelongsTo; + + const chat = await Chat.findOne({ + _id: chatId, + }).lean(); + + if (chat) { + const currentUserIsChatMember = chat.users.some((user) => + user.equals(currentUserId), + ); + return currentUserIsChatMember; + } else { + return false; + } +}; +/** + * This property included a `subscribe` method, which is used to + * subscribe the `receiver` and `sender` to receive Chat updates. + * + * @remarks To control updates on a per-client basis, the function uses the `withFilter` + * method imported from `apollo-server-express` module. + * You can learn about `subscription` {@link https://www.apollographql.com/docs/apollo-server/data/subscriptions/ | here }. + */ +export const messageSentToChat: SubscriptionResolvers["messageSentToChat"] = { + // @ts-expect-error-ts-ignore + subscribe: withFilter( + (_parent, _args, context) => + context.pubsub.asyncIterator([MESSAGE_SENT_TO_CHAT]), + + (payload, variables) => filterFunction(payload, variables), + ), +}; diff --git a/src/resolvers/Subscription/messageSentToDirectChat.ts b/src/resolvers/Subscription/messageSentToDirectChat.ts deleted file mode 100644 index 55ce56e603e..00000000000 --- a/src/resolvers/Subscription/messageSentToDirectChat.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { withFilter } from "graphql-subscriptions"; -import type { SubscriptionResolvers } from "../../types/generatedGraphQLTypes"; - -const MESSAGE_SENT_TO_DIRECT_CHAT = "MESSAGE_SENT_TO_DIRECT_CHAT"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const filterFunction = function (payload: any, variables: any): boolean { - const currentUserId = variables.userId.toString(); - console.log( - currentUserId, - payload.messageSentToDirectChat.receiver.toString(), - payload.messageSentToDirectChat.sender.toString(), - ); - return ( - currentUserId === payload.messageSentToDirectChat.receiver.toString() || - currentUserId === payload.messageSentToDirectChat.sender.toString() - ); -}; -/** - * This property included a `subscribe` method, which is used to - * subscribe the `receiver` and `sender` to receive Direct Chat updates. - * - * @remarks To control updates on a per-client basis, the function uses the `withFilter` - * method imported from `apollo-server-express` module. - * You can learn about `subscription` {@link https://www.apollographql.com/docs/apollo-server/data/subscriptions/ | here }. - */ -export const messageSentToDirectChat: SubscriptionResolvers["messageSentToDirectChat"] = - { - // @ts-expect-error-ts-ignore - subscribe: withFilter( - (_parent, _args, context) => - context.pubsub.asyncIterator([MESSAGE_SENT_TO_DIRECT_CHAT]), - - (payload, variables) => filterFunction(payload, variables), - ), - }; diff --git a/src/resolvers/Subscription/messageSentToGroupChat.ts b/src/resolvers/Subscription/messageSentToGroupChat.ts deleted file mode 100644 index ec45eb30c29..00000000000 --- a/src/resolvers/Subscription/messageSentToGroupChat.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { withFilter } from "graphql-subscriptions"; -import type { SubscriptionResolvers } from "../../types/generatedGraphQLTypes"; -import { GroupChat } from "../../models"; - -const MESSAGE_SENT_TO_GROUP_CHAT = "MESSAGE_SENT_TO_GROUP_CHAT"; - -/** - * This function is used to filter the subscription payload based on the current user's membership in the group chat. - * - * @param payload - The payload of the subscription message. - * @param context - The context object containing the current user's ID. - * @returns A promise that resolves to a boolean value indicating whether the current user is a member of the group chat. - */ -export const filterFunction = async function ( - payload: any, - variables: any, -): Promise { - const currentUserId = variables.userId; - const groupChatId = payload.messageSentToGroupChat.groupChatMessageBelongsTo; - - const groupChat = await GroupChat.findOne({ - _id: groupChatId, - }).lean(); - - if (groupChat) { - const currentUserIsGroupChatMember = groupChat.users.some((user) => - user.equals(currentUserId), - ); - return currentUserIsGroupChatMember; - } else { - return false; - } -}; -/** - * This property included a `subscribe` method, which is used to - * subscribe the `current_user` to get updates for Group chats. - * - * @remarks To control updates on a per-client basis, the function uses the `withFilter` - * method imported from `apollo-server-express` module. - * You can learn about `subscription` {@link https://www.apollographql.com/docs/apollo-server/data/subscriptions/ | here }. - */ -export const messageSentToGroupChat: SubscriptionResolvers["messageSentToGroupChat"] = - { - // @ts-expect-error-ts-ignore - subscribe: withFilter( - (_parent, _args, context) => - context.pubsub.asyncIterator([MESSAGE_SENT_TO_GROUP_CHAT]), - - (payload, variables) => filterFunction(payload, variables), - ), - }; diff --git a/src/resolvers/User/index.ts b/src/resolvers/User/index.ts index 538fe582cf2..79d556c2b3a 100644 --- a/src/resolvers/User/index.ts +++ b/src/resolvers/User/index.ts @@ -1,8 +1,8 @@ import type { UserResolvers } from "../../types/generatedGraphQLTypes"; -// import { tagsAssignedWith } from "./tagsAssignedWith"; +import { tagsAssignedWith } from "./tagsAssignedWith"; import { posts } from "./posts"; export const User: UserResolvers = { - // tagsAssignedWith, + tagsAssignedWith, posts, }; diff --git a/src/resolvers/User/tagsAssignedWith.ts b/src/resolvers/User/tagsAssignedWith.ts new file mode 100644 index 00000000000..92212b1633e --- /dev/null +++ b/src/resolvers/User/tagsAssignedWith.ts @@ -0,0 +1,163 @@ +import type { UserResolvers } from "../../types/generatedGraphQLTypes"; +import type { + InterfaceOrganizationTagUser, + InterfaceTagUser, +} from "../../models"; +import { TagUser } from "../../models"; +import { + type DefaultGraphQLArgumentError, + type ParseGraphQLConnectionCursorArguments, + type ParseGraphQLConnectionCursorResult, + getCommonGraphQLConnectionFilter, + getCommonGraphQLConnectionSort, + parseGraphQLConnectionArguments, + transformToDefaultGraphQLConnection, +} from "../../utilities/graphQLConnection"; +import { GraphQLError } from "graphql"; +import { MAXIMUM_FETCH_LIMIT } from "../../constants"; +import type { Types } from "mongoose"; + +/** + * Resolver function for the `tagsAssignedWith` field of a `User`. + * + * This resolver is used to resolve the `tagsAssignedWith` field of a `User` type. + * + * @param parent - The parent object representing the user. It contains information about the user, including the ID of the user. + * @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the tags assigned to the user. + * @returns A promise that resolves to a connection object containing the tags assigned to the user. + * + * @see TagUser - The TagUser model used to interact with the tag users collection in the database. + * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). + * @see transformToDefaultGraphQLConnection - The function used to transform the list of tags assigned to the user into a connection object. + * @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. + * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see MAXIMUM_FETCH_LIMIT - The maximum number of users that can be fetched in a single request. + * @see GraphQLError - The error class used to throw GraphQL errors. + * @see UserTagResolvers - The type definition for the resolvers of the User fields. + * + */ +export const tagsAssignedWith: UserResolvers["tagsAssignedWith"] = async ( + parent, + args, +) => { + const parseGraphQLConnectionArgumentsResult = + await parseGraphQLConnectionArguments({ + args, + parseCursor: /* c8 ignore start */ (args) => + parseCursor({ + ...args, + userId: parent._id, + }), + /* c8 ignore stop */ + maximumLimit: MAXIMUM_FETCH_LIMIT, + }); + + if (!parseGraphQLConnectionArgumentsResult.isSuccessful) { + throw new GraphQLError("Invalid arguments provided.", { + extensions: { + code: "INVALID_ARGUMENTS", + errors: parseGraphQLConnectionArgumentsResult.errors, + }, + }); + } + + const { parsedArgs } = parseGraphQLConnectionArgumentsResult; + + const filter = getCommonGraphQLConnectionFilter({ + cursor: parsedArgs.cursor, + direction: parsedArgs.direction, + }); + + const sort = getCommonGraphQLConnectionSort({ + direction: parsedArgs.direction, + }); + + const [objectList, totalCount] = await Promise.all([ + TagUser.find({ + ...filter, + userId: parent._id, + organizationId: args.organizationId, + }) + .sort(sort) + .limit(parsedArgs.limit) + .populate("tagId") + .lean() + .exec(), + + TagUser.find({ + userId: parent._id, + organizationId: args.organizationId, + }) + .countDocuments() + .exec(), + ]); + + return transformToDefaultGraphQLConnection< + ParsedCursor, + InterfaceTagUser, + InterfaceOrganizationTagUser + >({ + createNode: (object) => { + return object.tagId as InterfaceOrganizationTagUser; + }, + objectList, + parsedArgs, + totalCount, + }); +}; + +/* +This is typescript type of the parsed cursor for this connection resolver. +*/ +type ParsedCursor = string; + +/** + * Parses the cursor value for the `tagsAssignedWith` connection resolver. + * + * This function is used to parse the cursor value provided to the `tagsAssignedWith` connection resolver. + * + * @param cursorValue - The cursor value to be parsed. + * @param cursorName - The name of the cursor argument. + * @param cursorPath - The path of the cursor argument in the GraphQL query. + * @param userId - The ID of the user for which assigned tags are being queried. + * @returns An object containing the parsed cursor value or an array of errors if the cursor value is invalid. + * + * @see TagUser - The TagUser model used to interact with the tag users collection in the database. + * @see DefaultGraphQLArgumentError - The type definition for the default GraphQL argument error. + * @see ParseGraphQLConnectionCursorArguments - The type definition for the arguments provided to the parseCursor function. + * @see ParseGraphQLConnectionCursorResult - The type definition for the result of the parseCursor function. + * + */ +export const parseCursor = async ({ + cursorValue, + cursorName, + cursorPath, + userId, +}: ParseGraphQLConnectionCursorArguments & { + userId: string | Types.ObjectId; +}): ParseGraphQLConnectionCursorResult => { + const errors: DefaultGraphQLArgumentError[] = []; + const tagUser = await TagUser.findOne({ + _id: cursorValue, + userId, + }); + + if (!tagUser) { + errors.push({ + message: `Argument ${cursorName} is an invalid cursor.`, + path: cursorPath, + }); + } + + if (errors.length !== 0) { + return { + errors, + isSuccessful: false, + }; + } + + return { + isSuccessful: true, + parsedCursor: cursorValue, + }; +}; diff --git a/src/resolvers/UserTag/ancestorTags.ts b/src/resolvers/UserTag/ancestorTags.ts new file mode 100644 index 00000000000..890a6cd51ec --- /dev/null +++ b/src/resolvers/UserTag/ancestorTags.ts @@ -0,0 +1,41 @@ +import type { InterfaceOrganizationTagUser } from "../../models"; +import { OrganizationTagUser } from "../../models"; +import type { UserTagResolvers } from "../../types/generatedGraphQLTypes"; + +/** + * Resolver function for the `ancestorTags` field of an `OrganizationTagUser`. + * + * This function retrieves the ancestor tags of a specific organization user tag by recursively finding + * each parent tag until the root tag (where parentTagId is null) is reached. It then reverses the order, + * appends the current tag at the end, and returns the final array of tags. + * + * @param parent - The parent object representing the user tag. It contains information about the tag, including its ID and parentTagId. + * @returns A promise that resolves to the ordered array of ancestor tag documents found in the database. + */ +export const ancestorTags: UserTagResolvers["ancestorTags"] = async ( + parent, +) => { + // Initialize an array to collect the ancestor tags + const ancestorTags: InterfaceOrganizationTagUser[] = []; + + // Start with the current parentTagId + let currentParentId = parent.parentTagId; + + // Traverse up the hierarchy to find all ancestorTags + while (currentParentId) { + const tag = await OrganizationTagUser.findById(currentParentId).lean(); + + if (!tag) break; + + // Add the found tag to the ancestorTags array + ancestorTags.push(tag); + + // Move up to the next parent + currentParentId = tag.parentTagId; + } + + // Reverse the ancestorTags to have the root tag first, then append the current tag + ancestorTags.reverse(); + + return ancestorTags; +}; diff --git a/src/resolvers/UserTag/childTags.ts b/src/resolvers/UserTag/childTags.ts index ae95fb2b166..3c886d35d2f 100644 --- a/src/resolvers/UserTag/childTags.ts +++ b/src/resolvers/UserTag/childTags.ts @@ -2,9 +2,7 @@ import type { UserTagResolvers } from "../../types/generatedGraphQLTypes"; import type { InterfaceOrganizationTagUser } from "../../models"; import { OrganizationTagUser } from "../../models"; import { - getCommonGraphQLConnectionFilter, - getCommonGraphQLConnectionSort, - parseGraphQLConnectionArguments, + parseGraphQLConnectionArgumentsWithSortedByAndWhere, transformToDefaultGraphQLConnection, type DefaultGraphQLArgumentError, type ParseGraphQLConnectionCursorArguments, @@ -13,6 +11,12 @@ import { import { GraphQLError } from "graphql"; import { MAXIMUM_FETCH_LIMIT } from "../../constants"; import type { Types } from "mongoose"; +import { + getUserTagGraphQLConnectionFilter, + getUserTagGraphQLConnectionSort, + parseUserTagSortedBy, + parseUserTagWhere, +} from "../../utilities/userTagsPaginationUtils"; /** * Resolver function for the `childTags` field of a `UserTag`. @@ -26,8 +30,8 @@ import type { Types } from "mongoose"; * @see OrganizationTagUser - The OrganizationTagUser model used to interact with the organization tag users collection in the database. * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). * @see transformToDefaultGraphQLConnection - The function used to transform the list of child tags into a connection object. - * @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. - * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see getGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. + * @see getGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. * @see MAXIMUM_FETCH_LIMIT - The maximum number of child tags that can be fetched in a single request. * @see GraphQLError - The error class used to throw GraphQL errors. * @see UserTagResolvers - The type definition for the resolvers of the UserTag fields. @@ -37,14 +41,20 @@ export const childTags: UserTagResolvers["childTags"] = async ( parent, args, ) => { + const parseWhereResult = parseUserTagWhere(args.where); + const parseSortedByResult = parseUserTagSortedBy(args.sortedBy); + const parseGraphQLConnectionArgumentsResult = - await parseGraphQLConnectionArguments({ + await parseGraphQLConnectionArgumentsWithSortedByAndWhere({ args, - parseCursor: (args) => + parseSortedByResult, + parseWhereResult, + parseCursor: /* c8 ignore start */ (args) => parseCursor({ ...args, parentTagId: parent._id, }), + /* c8 ignore stop */ maximumLimit: MAXIMUM_FETCH_LIMIT, }); @@ -59,18 +69,27 @@ export const childTags: UserTagResolvers["childTags"] = async ( const { parsedArgs } = parseGraphQLConnectionArgumentsResult; - const filter = getCommonGraphQLConnectionFilter({ + const objectListFilter = getUserTagGraphQLConnectionFilter({ cursor: parsedArgs.cursor, direction: parsedArgs.direction, + sortById: parsedArgs.sort.sortById, + nameStartsWith: parsedArgs.filter.nameStartsWith, }); - const sort = getCommonGraphQLConnectionSort({ + // don't use _id as a filter in while counting the documents + // _id is only used for pagination + const totalCountFilter = Object.fromEntries( + Object.entries(objectListFilter).filter(([key]) => key !== "_id"), + ); + + const sort = getUserTagGraphQLConnectionSort({ direction: parsedArgs.direction, + sortById: parsedArgs.sort.sortById, }); const [objectList, totalCount] = await Promise.all([ OrganizationTagUser.find({ - ...filter, + ...objectListFilter, parentTagId: parent._id, }) .sort(sort) @@ -78,6 +97,7 @@ export const childTags: UserTagResolvers["childTags"] = async ( .lean() .exec(), OrganizationTagUser.find({ + ...totalCountFilter, parentTagId: parent._id, }) .countDocuments() diff --git a/src/resolvers/UserTag/index.ts b/src/resolvers/UserTag/index.ts index ee654046104..c2e8c73c5db 100644 --- a/src/resolvers/UserTag/index.ts +++ b/src/resolvers/UserTag/index.ts @@ -2,11 +2,15 @@ import type { UserTagResolvers } from "../../types/generatedGraphQLTypes"; import { childTags } from "./childTags"; import { organization } from "./organization"; import { parentTag } from "./parentTag"; +import { ancestorTags } from "./ancestorTags"; import { usersAssignedTo } from "./usersAssignedTo"; +import { usersToAssignTo } from "./usersToAssignTo"; export const UserTag: UserTagResolvers = { childTags, organization, parentTag, + ancestorTags, usersAssignedTo, + usersToAssignTo, }; diff --git a/src/resolvers/UserTag/usersAssignedTo.ts b/src/resolvers/UserTag/usersAssignedTo.ts index ebc8dd20a66..8722c94af2b 100644 --- a/src/resolvers/UserTag/usersAssignedTo.ts +++ b/src/resolvers/UserTag/usersAssignedTo.ts @@ -5,14 +5,18 @@ import { type DefaultGraphQLArgumentError, type ParseGraphQLConnectionCursorArguments, type ParseGraphQLConnectionCursorResult, - getCommonGraphQLConnectionFilter, - getCommonGraphQLConnectionSort, - parseGraphQLConnectionArguments, + parseGraphQLConnectionArgumentsWithSortedByAndWhere, transformToDefaultGraphQLConnection, } from "../../utilities/graphQLConnection"; import { GraphQLError } from "graphql"; import { MAXIMUM_FETCH_LIMIT } from "../../constants"; -import type { Types } from "mongoose"; +import { Types } from "mongoose"; +import { + parseUserTagSortedBy, + parseUserTagMemberWhere, + getUserTagMemberGraphQLConnectionFilter, + getUserTagGraphQLConnectionSort, +} from "../../utilities/userTagsPaginationUtils"; /** * Resolver function for the `usersAssignedTo` field of a `UserTag`. @@ -37,14 +41,20 @@ export const usersAssignedTo: UserTagResolvers["usersAssignedTo"] = async ( parent, args, ) => { + const parseWhereResult = parseUserTagMemberWhere(args.where); + const parseSortedByResult = parseUserTagSortedBy(args.sortedBy); + const parseGraphQLConnectionArgumentsResult = - await parseGraphQLConnectionArguments({ + await parseGraphQLConnectionArgumentsWithSortedByAndWhere({ args, - parseCursor: (args) => + parseSortedByResult, + parseWhereResult, + parseCursor: /* c8 ignore start */ (args) => parseCursor({ ...args, tagId: parent._id, }), + /* c8 ignore stop */ maximumLimit: MAXIMUM_FETCH_LIMIT, }); @@ -59,31 +69,91 @@ export const usersAssignedTo: UserTagResolvers["usersAssignedTo"] = async ( const { parsedArgs } = parseGraphQLConnectionArgumentsResult; - const filter = getCommonGraphQLConnectionFilter({ + const objectListFilter = getUserTagMemberGraphQLConnectionFilter({ cursor: parsedArgs.cursor, direction: parsedArgs.direction, + sortById: parsedArgs.sort.sortById, + firstNameStartsWith: parsedArgs.filter.firstNameStartsWith, + lastNameStartsWith: parsedArgs.filter.lastNameStartsWith, }); - const sort = getCommonGraphQLConnectionSort({ + // Separate the _id filter from the rest + // _id filter will be applied on the TagUser model + // the rest on the User model referenced by the userId field + const { _id: tagUserIdFilter, ...userFilter } = objectListFilter; + const tagUserFilter = tagUserIdFilter ? { _id: tagUserIdFilter } : {}; + + const sort = getUserTagGraphQLConnectionSort({ direction: parsedArgs.direction, + sortById: parsedArgs.sort.sortById, }); + // create the filter object to match the user documents in the pipeline + const userMatchFilter = Object.fromEntries( + Object.entries(userFilter).map(([key, value]) => [`user.${key}`, value]), + ); + + // commonPipeline object for the query + const commonPipeline = [ + // Perform a left join with User collection on _id + { + $lookup: { + from: "users", + localField: "userId", + foreignField: "_id", + as: "user", + }, + }, + { + $unwind: "$user", + }, + // apply the userFilter to the user + { + $match: { + ...userMatchFilter, + }, + }, + ]; + + // we want to assign the userFilter to the user referenced by userId + // and the _id filter (specified by the after/before) to the TagUser document const [objectList, totalCount] = await Promise.all([ - TagUser.find({ - ...filter, - tagId: parent._id, - }) - .sort(sort) - .limit(parsedArgs.limit) - .populate("userId") - .lean() - .exec(), - - TagUser.find({ - tagId: parent._id, - }) - .countDocuments() - .exec(), + // First aggregation to get the TagUser list matching the filter criteria + TagUser.aggregate([ + { + $match: { + ...tagUserFilter, + tagId: new Types.ObjectId(parent._id), + }, + }, + ...commonPipeline, + { + $sort: sort, + }, + { + $limit: parsedArgs.limit, + }, + { + $addFields: { userId: "$user" }, + }, + { + $project: { + user: 0, + }, + }, + ]), + // Second aggregation to count the total TagUser documents matching the filter criteria + TagUser.aggregate([ + { + $match: { + tagId: new Types.ObjectId(parent._id), + }, + }, + ...commonPipeline, + { + $count: "totalCount", + }, + ]).then((res) => res[0]?.totalCount || 0), ]); return transformToDefaultGraphQLConnection< diff --git a/src/resolvers/UserTag/usersToAssignTo.ts b/src/resolvers/UserTag/usersToAssignTo.ts new file mode 100644 index 00000000000..dfadc04264e --- /dev/null +++ b/src/resolvers/UserTag/usersToAssignTo.ts @@ -0,0 +1,199 @@ +import type { UserTagResolvers } from "../../types/generatedGraphQLTypes"; +import type { InterfaceUser } from "../../models"; +import { User } from "../../models"; +import type { + DefaultGraphQLArgumentError, + ParseGraphQLConnectionCursorArguments, + ParseGraphQLConnectionCursorResult, +} from "../../utilities/graphQLConnection"; + +import { + getCommonGraphQLConnectionSort, + parseGraphQLConnectionArgumentsWithWhere, + transformToDefaultGraphQLConnection, +} from "../../utilities/graphQLConnection"; + +import { GraphQLError } from "graphql"; +import { MAXIMUM_FETCH_LIMIT } from "../../constants"; +import { Types } from "mongoose"; +import { + getUserTagMemberGraphQLConnectionFilter, + parseUserTagMemberWhere, +} from "../../utilities/userTagsPaginationUtils"; + +/** + * Resolver function for the `usersToAssignTo` field of a `UserTag`. + * + * @param parent - The parent object representing the user tag. It contains information about the user tag, including the ID of the user tag. + * @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the users assigned to the user tag. + * @returns A promise that resolves to a connection object containing the users assigned to the user tag. + * + * @see User - The User model used to interact with the users collection in the database. + * @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). + * @see transformToDefaultGraphQLConnection - The function used to transform the list of users assigned to the user tag into a connection object. + * @see getGraphQLConnectionFilter - The function used to get the filter object for the GraphQL connection. + * @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. + * @see MAXIMUM_FETCH_LIMIT - The maximum number of users that can be fetched in a single request. + * @see GraphQLError - The error class used to throw GraphQL errors. + * @see UserResolvers - The type definition for the resolvers of the UserTag fields. + * + */ +export const usersToAssignTo: UserTagResolvers["usersToAssignTo"] = async ( + parent, + args, +) => { + const parseWhereResult = parseUserTagMemberWhere(args.where); + + const parseGraphQLConnectionArgumentsResult = + await parseGraphQLConnectionArgumentsWithWhere({ + args, + parseWhereResult, + parseCursor: /* c8 ignore start */ (args) => + parseCursor({ + ...args, + }), + /* c8 ignore stop */ + maximumLimit: MAXIMUM_FETCH_LIMIT, + }); + + if (!parseGraphQLConnectionArgumentsResult.isSuccessful) { + throw new GraphQLError("Invalid arguments provided.", { + extensions: { + code: "INVALID_ARGUMENTS", + errors: parseGraphQLConnectionArgumentsResult.errors, + }, + }); + } + + const { parsedArgs } = parseGraphQLConnectionArgumentsResult; + + const objectListFilter = getUserTagMemberGraphQLConnectionFilter({ + cursor: parsedArgs.cursor, + direction: parsedArgs.direction, + sortById: "DESCENDING", + firstNameStartsWith: parsedArgs.where.firstNameStartsWith, + lastNameStartsWith: parsedArgs.where.lastNameStartsWith, + }); + + // don't use _id as a filter in while counting the documents + // _id is only used for pagination + const totalCountFilter = Object.fromEntries( + Object.entries(objectListFilter).filter(([key]) => key !== "_id"), + ); + + const sort = getCommonGraphQLConnectionSort({ + direction: parsedArgs.direction, + }); + + const commonPipeline = [ + // Perform a left join with TagUser collection on userId + { + $lookup: { + from: "tagusers", // Name of the collection holding TagUser documents + localField: "_id", + foreignField: "userId", + as: "tagUsers", + }, + }, + // Filter out users that have a tagUser document with the specified tagId + { + $match: { + tagUsers: { + $not: { + $elemMatch: { tagId: new Types.ObjectId(parent._id) }, + }, + }, + }, + }, + ]; + + // Execute the queries using the common pipeline + const [objectList, totalCount] = await Promise.all([ + // First aggregation to get the user list matching the filter criteria + User.aggregate([ + { + $match: { + ...objectListFilter, + joinedOrganizations: { $in: [parent.organizationId] }, + }, + }, + ...commonPipeline, + { + $sort: { ...sort }, + }, + { $limit: parsedArgs.limit }, + ]), + // Second aggregation to count total users matching the filter criteria + User.aggregate([ + { + $match: { + ...totalCountFilter, + joinedOrganizations: { $in: [parent.organizationId] }, + }, + }, + ...commonPipeline, + { $count: "totalCount" }, + ]).then((res) => res[0]?.totalCount || 0), + ]); + + return transformToDefaultGraphQLConnection< + ParsedCursor, + InterfaceUser, + InterfaceUser + >({ + objectList, + parsedArgs, + totalCount, + }); +}; + +/* +This is typescript type of the parsed cursor for this connection resolver. +*/ +type ParsedCursor = string; + +/** + * Parses the cursor value for the `usersToAssignTo` connection resolver. + * + * This function is used to parse the cursor value provided to the `usersToAssignTo` connection resolver. + * + * @param cursorValue - The cursor value to be parsed. + * @param cursorName - The name of the cursor argument. + * @param cursorPath - The path of the cursor argument in the GraphQL query. + * @returns An object containing the parsed cursor value or an array of errors if the cursor value is invalid. + * + * @see User - The User model used to interact with the users collection in the database. + * @see DefaultGraphQLArgumentError - The type definition for the default GraphQL argument error. + * @see ParseGraphQLConnectionCursorArguments - The type definition for the arguments provided to the parseCursor function. + * @see ParseGraphQLConnectionCursorResult - The type definition for the result of the parseCursor function. + * + */ +export const parseCursor = async ({ + cursorValue, + cursorName, + cursorPath, +}: ParseGraphQLConnectionCursorArguments): ParseGraphQLConnectionCursorResult => { + const errors: DefaultGraphQLArgumentError[] = []; + const user = await User.findOne({ + _id: cursorValue, + }); + + if (!user) { + errors.push({ + message: `Argument ${cursorName} is an invalid cursor.`, + path: cursorPath, + }); + } + + if (errors.length !== 0) { + return { + errors, + isSuccessful: false, + }; + } + + return { + isSuccessful: true, + parsedCursor: cursorValue, + }; +}; diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index 38873057ba0..ef021d86eff 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -18,14 +18,11 @@ import { AgendaSection } from "./AgendaSection"; import { AgendaCategory } from "./AgendaCategory"; import { CheckIn } from "./CheckIn"; import { Comment } from "./Comment"; -import { DirectChat } from "./DirectChat"; -import { DirectChatMessage } from "./DirectChatMessage"; +import { Chat } from "./Chat"; +import { ChatMessage } from "./ChatMessage"; import { Event } from "./Event"; -import { EventVolunteer } from "./EventVolunteer"; import { Feedback } from "./Feedback"; import { Fund } from "./Fund"; -import { GroupChat } from "./GroupChat"; -import { GroupChatMessage } from "./GroupChatMessage"; import { MembershipRequest } from "./MembershipRequest"; import { Mutation } from "./Mutation"; import { Organization } from "./Organization"; @@ -50,15 +47,12 @@ const resolvers: Resolvers = { Advertisement, CheckIn, Comment, - DirectChat, - DirectChatMessage, + Chat, + ChatMessage, Event, - EventVolunteer, Feedback, Fund, - GroupChat, UserFamily, - GroupChatMessage, MembershipRequest, Mutation, Organization, @@ -89,8 +83,7 @@ const resolversComposition = { "Mutation.addOrganizationImage": [currentUserExists()], "Mutation.blockPluginCreationBySuperadmin": [currentUserExists()], "Mutation.createComment": [currentUserExists()], - "Mutation.createDirectChat": [currentUserExists()], - "Mutation.createGroupChat": [currentUserExists()], + "Mutation.createChat": [currentUserExists()], "Mutation.createOrganization": [currentUserExists()], "Mutation.createVenue": [currentUserExists()], "Mutation.deleteVenue": [currentUserExists()], diff --git a/src/setup/getMinioBinaryUrl.ts b/src/setup/getMinioBinaryUrl.ts new file mode 100644 index 00000000000..08f05c9a060 --- /dev/null +++ b/src/setup/getMinioBinaryUrl.ts @@ -0,0 +1,27 @@ +import * as os from "os"; + +const platform = os.platform(); + +/** + * Constructs the URL to download the MinIO binary for the current platform. + * + * @returns The URL of the MinIO binary for the current platform. + * @throws Error If the platform is unsupported. + */ +export const getMinioBinaryUrl = (): string => { + let platformPath: string; + switch (platform) { + case "win32": + platformPath = "windows-amd64/minio.exe"; + break; + case "darwin": + platformPath = "darwin-amd64/minio"; + break; + case "linux": + platformPath = "linux-amd64/minio"; + break; + default: + throw new Error(`Unsupported platform: ${platform}`); + } + return `https://dl.min.io/server/minio/release/${platformPath}`; +}; diff --git a/src/setup/installMinio.ts b/src/setup/installMinio.ts new file mode 100644 index 00000000000..1cfedffd8eb --- /dev/null +++ b/src/setup/installMinio.ts @@ -0,0 +1,70 @@ +import * as os from "os"; +import * as fs from "fs"; +import * as path from "path"; +import * as https from "https"; +import * as dotenv from "dotenv"; +import { setPathEnvVar } from "./setPathEnvVar"; +import { getMinioBinaryUrl } from "./getMinioBinaryUrl"; + +dotenv.config(); + +const platform = os.platform(); + +/** + * Installs MinIO by downloading the binary, saving it to a local directory, and setting appropriate permissions. + * + * @returns A promise that resolves with the path to the installed MinIO binary. + * @throws Error If the download or installation fails. + */ +export const installMinio = (): Promise => { + return new Promise((resolve, reject) => { + const installDir = path.join(os.homedir(), ".minio"); + + if (!fs.existsSync(installDir)) { + try { + fs.mkdirSync(installDir, { recursive: true }); + } catch (err: unknown) { + if (err instanceof Error) { + return reject( + new Error( + `Failed to create directory ${installDir}: ${err.message}`, + ), + ); + } + } + } + const minioPath = path.join( + installDir, + `minio${platform === "win32" ? ".exe" : ""}`, + ); + + const file = fs.createWriteStream(minioPath); + + https + .get(getMinioBinaryUrl(), (response) => { + response.pipe(file); + file.on("finish", () => { + file.close(() => { + try { + fs.chmodSync(minioPath, 0o755); + setPathEnvVar(installDir); + } catch (err: unknown) { + if (err instanceof Error) { + return reject( + new Error( + `Failed to set permissions or PATH environment variable: ${err.message}`, + ), + ); + } + } + + resolve(minioPath); + }); + }); + }) + .on("error", (err) => { + fs.unlinkSync(minioPath); + reject(new Error(`Failed to download Minio binary: ${err.message}`)); + }); + }); +}; diff --git a/src/setup/isMinioInstalled.ts b/src/setup/isMinioInstalled.ts new file mode 100644 index 00000000000..7c2ac411f3a --- /dev/null +++ b/src/setup/isMinioInstalled.ts @@ -0,0 +1,35 @@ +import * as os from "os"; +import * as fs from "fs"; +import * as path from "path"; +import { execSync } from "child_process"; +import { setPathEnvVar } from "./setPathEnvVar"; + +const platform = os.platform(); + +/** + * Checks if MinIO is installed either via the command line or by checking the existence of the MinIO binary. + * If installed, it sets the PATH environment variable. + * + * @returns A boolean indicating whether MinIO is installed. + */ +export const isMinioInstalled = (): boolean => { + const installDir = path.join(os.homedir(), ".minio"); + const minioPath = path.join( + installDir, + `minio${platform === "win32" ? ".exe" : ""}`, + ); + + try { + execSync("minio --version", { stdio: "ignore" }); + setPathEnvVar(installDir); + return true; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (err) { + if (fs.existsSync(minioPath)) { + setPathEnvVar(installDir); + return true; + } + } + + return false; +}; diff --git a/src/setup/setPathEnvVar.ts b/src/setup/setPathEnvVar.ts new file mode 100644 index 00000000000..7d88fd4573a --- /dev/null +++ b/src/setup/setPathEnvVar.ts @@ -0,0 +1,44 @@ +// pathUtils.ts +import * as os from "os"; +import { execSync, spawnSync } from "child_process"; + +const platform = os.platform(); + +/** + * Sets the PATH environment variable to include the directory where MinIO is installed. + * + * This function modifies the PATH environment variable to include the specified installation directory. + * It handles different platforms: + * - On Windows, it uses `setx` to update the system PATH variable. + * - On Unix-based systems (macOS and Linux), it appends the directory to the PATH in the current shell session + * and writes the update to the shell configuration file (`~/.bashrc` for Linux, `~/.zshrc` for macOS). + * + * **Assumption:** + * This function assumes that the shell configuration file (`.bashrc` or `.zshrc`) already exists. In most typical + * development environments, these files are present. If the file does not exist, users may need to create it manually + * to ensure the PATH update is applied in future shell sessions. + * + * @param installDir - The directory where MinIO is installed. + * @throws Error If updating the PATH environment variable fails. + */ +export const setPathEnvVar = (installDir: string): void => { + try { + if (platform === "win32") { + const pathEnvVar = `${process.env.PATH};${installDir}`; + spawnSync("setx", ["PATH", pathEnvVar], { + shell: true, + stdio: "inherit", + }); + } else { + process.env.PATH = `${process.env.PATH}:${installDir}`; + const shellConfigFile = platform === "darwin" ? "~/.zshrc" : "~/.bashrc"; + execSync(`echo 'export PATH=$PATH:${installDir}' >> ${shellConfigFile}`); + } + } catch (err: unknown) { + console.log(err); + if (err instanceof Error) + throw new Error( + `Failed to set PATH environment variable: ${err.message}`, + ); + } +}; diff --git a/src/setup/superAdmin.ts b/src/setup/superAdmin.ts index d1184a5f925..66fb954c45f 100644 --- a/src/setup/superAdmin.ts +++ b/src/setup/superAdmin.ts @@ -16,7 +16,7 @@ export async function askForSuperAdminEmail(): Promise { name: "email", message: "Enter the email which you wish to assign as the Super Admin of last resort :", - validate: (input: string) => + validate: (input: string): boolean | string => isValidEmail(input) || "Invalid email. Please try again.", }, ]); diff --git a/src/setup/updateIgnoreFile.ts b/src/setup/updateIgnoreFile.ts new file mode 100644 index 00000000000..944e5ac1e5c --- /dev/null +++ b/src/setup/updateIgnoreFile.ts @@ -0,0 +1,64 @@ +import * as fs from "fs"; +import path from "path"; + +/** + * Updates the specified ignore file by adding an ignore pattern for a given directory. + * + * This function ensures that the directory to be ignored is relative to the project root. + * It reads the current content of the ignore file, removes any existing entries for the MinIO data directory, + * and appends a new entry if it does not already exist. + * + * @param filePath - The path to the ignore file to be updated. + * @param directoryToIgnore - The directory path that should be ignored, relative to the project root. + * + * @returns void + * + * @remarks + * If the directory is outside the project root, the function will return early without making changes. + * No logging is performed for cases where the ignore pattern already exists in the file, as this is expected behavior. + */ +export const updateIgnoreFile = ( + filePath: string, + directoryToIgnore: string, +): void => { + const projectRoot = process.cwd(); + const relativePath = path.relative(projectRoot, directoryToIgnore); + const ignorePattern = relativePath + "/**"; + + const isInsideProjectRoot = + !relativePath.startsWith("..") && !path.isAbsolute(relativePath); + + // If the directory is outside the project root, simply return without doing anything. + if (!isInsideProjectRoot) { + return; + } + + let content = ""; + + if (fs.existsSync(filePath)) { + content = fs.readFileSync(filePath, "utf8"); + } + + // If the ignorePattern already exists in the content, return early. + // There's no need to modify the file if the pattern is already present, as it's redundant to add the same pattern again. + // No log is necessary here because this is a normal, expected case where no changes are needed. + if (content.includes(ignorePattern)) { + return; + } + + /** + * This regex looks for: + * 1. A line starting with "# MinIO data directory" followed by a newline (\\n). + * 2. Any path (one or more non-newline characters [^\\n]+) followed by "/**" (escaped as \/ and \*). + * 3. It matches the entire pattern up to the next newline (\\n), allowing us to remove the MinIO data entry. + */ + const ignorePatternRegex = /# MinIO data directory\n[^\n]+\/\*\*\n/g; + + content = content.replace(ignorePatternRegex, ""); + + if (!content.includes(ignorePattern)) { + content += `\n# MinIO data directory\n${ignorePattern}\n`; + fs.writeFileSync(filePath, content); + console.log(`Updated ${filePath} with MinIO data directory.`); + } +}; diff --git a/src/typeDefs/enums.ts b/src/typeDefs/enums.ts index b3e0a8b4c2f..e8e294b0aba 100644 --- a/src/typeDefs/enums.ts +++ b/src/typeDefs/enums.ts @@ -141,6 +141,22 @@ export const enums = gql` endDate_DESC } + enum EventVolunteersOrderByInput { + hoursVolunteered_ASC + hoursVolunteered_DESC + } + + enum EventVolunteerGroupOrderByInput { + volunteers_ASC + volunteers_DESC + assignments_ASC + assignments_DESC + } + enum VolunteerMembershipOrderByInput { + createdAt_ASC + createdAt_DESC + } + enum WeekDays { MONDAY TUESDAY @@ -202,6 +218,11 @@ export const enums = gql` MENU } + enum FileVisibility { + PRIVATE + PUBLIC + } + enum ItemType { Regular Note diff --git a/src/typeDefs/errors/createDirectChatError.ts b/src/typeDefs/errors/createDirectChatError.ts deleted file mode 100644 index f2a9b9eb43b..00000000000 --- a/src/typeDefs/errors/createDirectChatError.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { gql } from "graphql-tag"; - -/** - * GraphQL schema definition for errors related to creating a direct chat. - */ -export const createDirectChatErrors = gql` - type OrganizationNotFoundError implements Error { - message: String! - } - - type UserNotFoundError implements Error { - message: String! - } - - union CreateDirectChatError = OrganizationNotFoundError | UserNotFoundError -`; diff --git a/src/typeDefs/errors/index.ts b/src/typeDefs/errors/index.ts index a71db2d6e37..7306e510273 100644 --- a/src/typeDefs/errors/index.ts +++ b/src/typeDefs/errors/index.ts @@ -3,7 +3,6 @@ import { connectionError } from "./connectionError"; import { createMemberErrors } from "./createMemberErrors"; import { createAdminErrors } from "./createAdminErrors"; import { createCommentErrors } from "./createCommentErrors"; -import { createDirectChatErrors } from "./createDirectChatError"; /** * Array of all error definitions. @@ -14,5 +13,4 @@ export const errors = [ createMemberErrors, createAdminErrors, createCommentErrors, - createDirectChatErrors, ]; diff --git a/src/typeDefs/inputs.ts b/src/typeDefs/inputs.ts index 65cca30e4d1..ae0958f69a2 100644 --- a/src/typeDefs/inputs.ts +++ b/src/typeDefs/inputs.ts @@ -16,15 +16,12 @@ export const inputs = gql` userId: ID! } - input createChatInput { - userIds: [ID!]! + input chatInput { + isGroup: Boolean! organizationId: ID - } - - input createGroupChatInput { userIds: [ID!]! - organizationId: ID! - title: String! + name: String + image: String } input createUserFamilyInput { @@ -41,8 +38,9 @@ export const inputs = gql` input CreateActionItemInput { assigneeId: ID! + assigneeType: String! preCompletionNotes: String - allotedHours: Float + allottedHours: Float dueDate: Date eventId: ID } @@ -73,6 +71,7 @@ export const inputs = gql` } input ActionItemWhereInput { + orgId: ID actionItemCategory_id: ID event_id: ID categoryName: String @@ -149,31 +148,54 @@ export const inputs = gql` input EventVolunteerInput { userId: ID! eventId: ID! - groupId: ID! + groupId: ID + } + + input EventVolunteerWhereInput { + id: ID + eventId: ID + groupId: ID + hasAccepted: Boolean + name_contains: String } input EventVolunteerGroupInput { - name: String + name: String! + description: String eventId: ID! + leaderId: ID! volunteersRequired: Int + volunteerUserIds: [ID!]! } input EventVolunteerGroupWhereInput { eventId: ID - volunteerId: ID + userId: ID + orgId: ID + leaderName: String name_contains: String } - input UpdateEventVolunteerInput { + input VolunteerMembershipWhereInput { + eventTitle: String + userName: String + status: String + userId: ID eventId: ID - isAssigned: Boolean - isInvited: Boolean - response: EventVolunteerResponse + groupId: ID + filter: String + } + + input UpdateEventVolunteerInput { + assignments: [ID] + hasAccepted: Boolean + isPublic: Boolean } input UpdateEventVolunteerGroupInput { - eventId: ID + eventId: ID! name: String + description: String volunteersRequired: Int } @@ -284,11 +306,11 @@ export const inputs = gql` id_starts_with: ID user: UserWhereInput - } - input MessageChatInput { - message: String! - receiver: ID! + creatorId: ID + creatorId_not: ID + creatorId_in: [ID!] + creatorId_not_in: [ID!] } input NoteInput { @@ -424,13 +446,36 @@ export const inputs = gql` tagId: ID! } + input AddPeopleToUserTagInput { + userIds: [ID!]! + tagId: ID! + } + + input TagActionsInput { + currentTagId: ID! + selectedTagIds: [ID!]! + } + + input UserTagWhereInput { + name: UserTagNameWhereInput + } + + input UserTagNameWhereInput { + starts_with: String! + } + + input UserTagSortedByInput { + id: SortedByOrder! + } + input UpdateActionItemInput { assigneeId: ID + assigneeType: String preCompletionNotes: String postCompletionNotes: String dueDate: Date completionDate: Date - allotedHours: Float + allottedHours: Float isCompleted: Boolean } @@ -600,6 +645,25 @@ export const inputs = gql` event_title_contains: String } + + input UserTagUsersAssignedToSortedByInput { + id: SortedByOrder! + } + + input UserTagUsersAssignedToWhereInput { + firstName: UserNameWhereInput + lastName: UserNameWhereInput + } + + input UserTagUsersToAssignToWhereInput { + firstName: UserNameWhereInput + lastName: UserNameWhereInput + } + + input UserNameWhereInput { + starts_with: String! + } + input PostUpdateInput { text: String title: String @@ -624,6 +688,20 @@ export const inputs = gql` file: String } + input VolunteerMembershipInput { + event: ID! + group: ID + status: String! + userId: ID! + } + + input VolunteerRankWhereInput { + nameContains: String + orderBy: String! + timeFrame: String! + limit: Int + } + input VenueWhereInput { name_contains: String name_starts_with: String diff --git a/src/typeDefs/mutations.ts b/src/typeDefs/mutations.ts index bca74770e53..5253da97831 100644 --- a/src/typeDefs/mutations.ts +++ b/src/typeDefs/mutations.ts @@ -33,18 +33,22 @@ export const mutations = gql` addUserImage(file: String!): User! @auth - addUserToGroupChat(userId: ID!, chatId: ID!): GroupChat! @auth - addUserToUserFamily(userId: ID!, familyId: ID!): UserFamily! @auth + addPeopleToUserTag(input: AddPeopleToUserTagInput!): UserTag + @auth + @role(requires: ADMIN) + + assignToUserTags(input: TagActionsInput!): UserTag + @auth + @role(requires: ADMIN) + removeUserFromUserFamily(userId: ID!, familyId: ID!): UserFamily! @auth removeUserFamily(familyId: ID!): UserFamily! @auth createUserFamily(data: createUserFamilyInput!): UserFamily! @auth - adminRemoveGroup(groupId: ID!): GroupChat! @auth - assignUserTag(input: ToggleUserTagAssignInput!): User @auth blockPluginCreationBySuperadmin( @@ -88,7 +92,7 @@ export const mutations = gql` createComment(postId: ID!, data: CommentInput!): Comment @auth - createDirectChat(data: createChatInput!): DirectChat! @auth + createChat(data: chatInput!): Chat createDonation( userId: ID! @@ -110,10 +114,6 @@ export const mutations = gql` data: FundCampaignPledgeInput! ): FundraisingCampaignPledge! @auth - createGroupChat(data: createGroupChatInput!): GroupChat! @auth - - createMessageChat(data: MessageChatInput!): MessageChat! @auth - createOrganization(data: OrganizationInput, file: String): Organization! @auth @role(requires: SUPERADMIN) @@ -139,6 +139,10 @@ export const mutations = gql` createVenue(data: VenueInput!): Venue @auth + createVolunteerMembership( + data: VolunteerMembershipInput! + ): VolunteerMembership! @auth + deleteAdvertisement(id: ID!): DeleteAdvertisementPayload deleteAgendaCategory(id: ID!): ID! @auth @@ -198,8 +202,6 @@ export const mutations = gql` removeComment(id: ID!): Comment @auth - removeDirectChat(chatId: ID!, organizationId: ID!): DirectChat! @auth - removeEvent( id: ID! recurringEventDeleteType: RecurringEventMutationType @@ -214,8 +216,6 @@ export const mutations = gql` removeEventVolunteerGroup(id: ID!): EventVolunteerGroup! @auth - removeGroupChat(chatId: ID!): GroupChat! @auth - removeMember(data: UserAndOrganizationInput!): Organization! @auth removeOrganization(id: ID!): UserData! @auth @role(requires: SUPERADMIN) @@ -234,10 +234,12 @@ export const mutations = gql` removeSampleOrganization: Boolean! @auth - removeUserFromGroupChat(userId: ID!, chatId: ID!): GroupChat! @auth - removeUserImage: User! @auth + removeFromUserTags(input: TagActionsInput!): UserTag + @auth + @role(requires: ADMIN) + resetCommunity: Boolean! @auth @role(requires: SUPERADMIN) revokeRefreshTokenForUser: Boolean! @auth @@ -246,15 +248,11 @@ export const mutations = gql` sendMembershipRequest(organizationId: ID!): MembershipRequest! @auth - sendMessageToDirectChat( - chatId: ID! - messageContent: String! - ): DirectChatMessage! @auth - - sendMessageToGroupChat( + sendMessageToChat( chatId: ID! messageContent: String! - ): GroupChatMessage! @auth + replyTo: ID + ): ChatMessage! @auth signUp(data: UserInput!, file: String): AuthData! @@ -312,9 +310,12 @@ export const mutations = gql` updateEventVolunteerGroup( id: ID! - data: UpdateEventVolunteerGroupInput + data: UpdateEventVolunteerGroupInput! ): EventVolunteerGroup! @auth + updateVolunteerMembership(id: ID!, status: String!): VolunteerMembership! + @auth + updateFundraisingCampaign( id: ID! data: UpdateFundCampaignInput! @@ -337,6 +338,8 @@ export const mutations = gql` updatePluginStatus(id: ID!, orgId: ID!): Plugin! + updateSessionTimeout(timeout: Int!): Boolean! @auth + updateUserTag(input: UpdateUserTagInput!): UserTag @auth updateUserProfile(data: UpdateUserInput, file: String): User! @auth diff --git a/src/typeDefs/queries.ts b/src/typeDefs/queries.ts index 4db86168591..3d7d5f0223a 100644 --- a/src/typeDefs/queries.ts +++ b/src/typeDefs/queries.ts @@ -16,6 +16,12 @@ export const queries = gql` orderBy: ActionItemsOrderByInput ): [ActionItem] + actionItemsByUser( + userId: ID! + where: ActionItemWhereInput + orderBy: ActionItemsOrderByInput + ): [ActionItem] + actionItemCategoriesByOrganization( organizationId: ID! where: ActionItemCategoryWhereInput @@ -44,15 +50,9 @@ export const queries = gql` customDataByOrganization(organizationId: ID!): [UserCustomData!]! - directChatsByUserID(id: ID!): [DirectChat] - - directChatById(id: ID!): DirectChat - - groupChatById(id: ID!): GroupChat + chatById(id: ID!): Chat! - groupChatsByUserId(id: ID!): [GroupChat] - - directChatsMessagesByChatID(id: ID!): [DirectChatMessage] + chatsByUserId(id: ID!): [Chat] event(id: ID!): Event @@ -60,17 +60,32 @@ export const queries = gql` eventsByOrganizationConnection( where: EventWhereInput + upcomingOnly: Boolean first: Int skip: Int orderBy: EventOrderByInput ): [Event!]! - eventVolunteersByEvent(id: ID!): [EventVolunteer] + getEventVolunteers( + where: EventVolunteerWhereInput! + orderBy: EventVolunteersOrderByInput + ): [EventVolunteer]! getEventVolunteerGroups( - where: EventVolunteerGroupWhereInput + where: EventVolunteerGroupWhereInput! + orderBy: EventVolunteerGroupOrderByInput ): [EventVolunteerGroup]! + getVolunteerMembership( + where: VolunteerMembershipWhereInput! + orderBy: VolunteerMembershipOrderByInput + ): [VolunteerMembership]! + + getVolunteerRanks( + orgId: ID! + where: VolunteerRankWhereInput! + ): [VolunteerRank]! + fundsByOrganization( organizationId: ID! where: FundWhereInput @@ -130,10 +145,10 @@ export const queries = gql` getUserTag(id: ID!): UserTag - getUserTagAncestors(id: ID!): [UserTag] - getAllNotesForAgendaItem(agendaItemId: ID!): [Note] + getRecurringEvents(baseRecurringEventId: ID!): [Event] + advertisementsConnection( after: String before: String @@ -200,5 +215,7 @@ export const queries = gql` ): [UserData]! @auth venue(id: ID!): Venue + + eventsAttendedByUser(id: ID, orderBy: EventOrderByInput): [Event] } `; diff --git a/src/typeDefs/subscriptions.ts b/src/typeDefs/subscriptions.ts index 4c718b97617..f5e3c89c2b4 100644 --- a/src/typeDefs/subscriptions.ts +++ b/src/typeDefs/subscriptions.ts @@ -3,9 +3,7 @@ import { gql } from "graphql-tag"; // Place fields alphabetically to ensure easier lookup and navigation. export const subscriptions = gql` type Subscription { - directMessageChat: MessageChat - messageSentToDirectChat(userId: ID!): DirectChatMessage - messageSentToGroupChat(userId: ID!): GroupChatMessage + messageSentToChat(userId: ID!): ChatMessage onPluginUpdate: Plugin } `; diff --git a/src/typeDefs/types.ts b/src/typeDefs/types.ts index 84f62be68fd..25b6f12b0fd 100644 --- a/src/typeDefs/types.ts +++ b/src/typeDefs/types.ts @@ -67,15 +67,19 @@ export const types = gql` createdBy: User updatedBy: User } + # Action Item for a ActionItemCategory type ActionItem { _id: ID! - assignee: User + assignee: EventVolunteer + assigneeGroup: EventVolunteerGroup + assigneeUser: User + assigneeType: String! assigner: User actionItemCategory: ActionItemCategory preCompletionNotes: String postCompletionNotes: String - allotedHours: Float + allottedHours: Float assignmentDate: Date! dueDate: Date! completionDate: Date! @@ -130,6 +134,7 @@ export const types = gql` logoUrl: String websiteLink: String socialMediaUrls: SocialMediaUrls + timeout: Int } type CreateAdminPayload { user: AppUserProfile @@ -164,11 +169,6 @@ export const types = gql` userErrors: [CreateCommentError!]! } - type createDirectChatPayload { - directChat: DirectChat - userErrors: [CreateDirectChatError!]! - } - type DeletePayload { success: Boolean! } @@ -177,26 +177,6 @@ export const types = gql` advertisement: Advertisement } - type DirectChat { - _id: ID! - users: [User!]! - messages: [DirectChatMessage] - creator: User - createdAt: DateTime! - updatedAt: DateTime! - organization: Organization - } - - type DirectChatMessage { - _id: ID! - directChatMessageBelongsTo: DirectChat! - sender: User! - receiver: User! - createdAt: DateTime! - updatedAt: DateTime! - messageContent: String! - } - type Donation { _id: ID! userId: ID! @@ -276,21 +256,36 @@ export const types = gql` feedback: [Feedback!]! averageFeedbackScore: Float agendaItems: [AgendaItem] + volunteers: [EventVolunteer] + volunteerGroups: [EventVolunteerGroup] } type EventVolunteer { _id: ID! - createdAt: DateTime! + user: User! creator: User event: Event - group: EventVolunteerGroup - isAssigned: Boolean - isInvited: Boolean - response: String - user: User! + groups: [EventVolunteerGroup] + hasAccepted: Boolean! + isPublic: Boolean! + hoursVolunteered: Float! + assignments: [ActionItem] + hoursHistory: [HoursHistory] + createdAt: DateTime! updatedAt: DateTime! } + type HoursHistory { + hours: Float! + date: Date! + } + + type VolunteerRank { + rank: Int! + user: User! + hoursVolunteered: Float! + } + type EventAttendee { _id: ID! userId: ID! @@ -307,14 +302,28 @@ export const types = gql` type EventVolunteerGroup { _id: ID! - createdAt: DateTime! creator: User event: Event leader: User! name: String + description: String + createdAt: DateTime! updatedAt: DateTime! volunteers: [EventVolunteer] volunteersRequired: Int + assignments: [ActionItem] + } + + type VolunteerMembership { + _id: ID! + status: String! + volunteer: EventVolunteer! + event: Event! + group: EventVolunteerGroup + createdBy: User + updatedBy: User + createdAt: DateTime! + updatedAt: DateTime! } type Feedback { @@ -326,6 +335,30 @@ export const types = gql` updatedAt: DateTime! } + type File { + _id: ID! + fileName: String! + mimeType: String! + size: Int! + hash: Hash! + uri: String! + referenceCount: Int! + metadata: FileMetadata! + encryption: Boolean! + archived: Boolean! + visibility: FileVisibility! + backupStatus: String! + status: Status! + createdAt: DateTime! + updatedAt: DateTime! + archivedAt: DateTime + } + + type FileMetadata { + objectKey: String! + bucketName: String! + } + type Fund { _id: ID! organizationId: ID! @@ -372,24 +405,9 @@ export const types = gql` admins: [User!]! } - type GroupChat { - _id: ID! - title: String! - users: [User!]! - messages: [GroupChatMessage] - creator: User - createdAt: DateTime! - updatedAt: DateTime! - organization: Organization! - } - - type GroupChatMessage { - _id: ID! - groupChatMessageBelongsTo: GroupChat! - sender: User! - createdAt: DateTime! - updatedAt: DateTime! - messageContent: String! + type Hash { + value: String! + algorithm: String! } type Language { @@ -423,16 +441,6 @@ export const types = gql` creator: User } - type MessageChat { - _id: ID! - sender: User! - receiver: User! - message: String! - languageBarrier: Boolean - createdAt: DateTime! - updatedAt: DateTime! - } - type Note { _id: ID! content: String! @@ -477,6 +485,8 @@ export const types = gql` before: String first: PositiveInt last: PositiveInt + where: UserTagWhereInput + sortedBy: UserTagSortedByInput ): UserTagsConnection posts( after: String @@ -563,8 +573,7 @@ export const types = gql` createdAt: DateTime! creator: User updatedAt: DateTime! - imageUrl: URL - videoUrl: URL + file: File organization: Organization! likedBy: [User] comments: [Comment] @@ -624,6 +633,7 @@ export const types = gql` type User { _id: ID! + identifier: Int! appUserProfileId: AppUserProfile address: Address birthDate: Date @@ -641,6 +651,7 @@ export const types = gql` firstName: String! gender: Gender image: String + file: File joinedOrganizations: [Organization] lastName: String! maritalStatus: MaritalStatus @@ -648,6 +659,7 @@ export const types = gql` phone: UserPhone membershipRequests: [MembershipRequest] registeredEvents: [Event] + eventsAttended: [Event] pluginCreationAllowed: Boolean! tagsAssignedWith( after: String @@ -716,6 +728,10 @@ export const types = gql` """ parentTag: UserTag """ + A field to traverse the ancestor tags of this UserTag. + """ + ancestorTags: [UserTag] + """ A connection field to traverse a list of UserTag this UserTag is a parent to. """ @@ -724,6 +740,8 @@ export const types = gql` before: String first: PositiveInt last: PositiveInt + where: UserTagWhereInput + sortedBy: UserTagSortedByInput ): UserTagsConnection """ A connection field to traverse a list of User this UserTag is assigned @@ -734,6 +752,20 @@ export const types = gql` before: String first: PositiveInt last: PositiveInt + where: UserTagUsersAssignedToWhereInput + sortedBy: UserTagUsersAssignedToSortedByInput + ): UsersConnection + + """ + A connection field to traverse a list of Users this UserTag is not assigned + to, to see and select among them and assign this tag. + """ + usersToAssignTo( + after: String + before: String + first: PositiveInt + last: PositiveInt + where: UserTagUsersToAssignToWhereInput ): UsersConnection } @@ -770,4 +802,30 @@ export const types = gql` cursor: String! node: User! } + + type Chat { + _id: ID! + isGroup: Boolean! + name: String + createdAt: DateTime! + creator: User + messages: [ChatMessage] + organization: Organization + updatedAt: DateTime! + users: [User!]! + admins: [User] + lastMessageId: String + image: String + } + + type ChatMessage { + _id: ID! + createdAt: DateTime! + chatMessageBelongsTo: Chat! + replyTo: ChatMessage + messageContent: String! + sender: User! + deletedBy: [User] + updatedAt: DateTime! + } `; diff --git a/src/typeDefs/unions.ts b/src/typeDefs/unions.ts index d9c6501960d..6d6dcbc9e31 100644 --- a/src/typeDefs/unions.ts +++ b/src/typeDefs/unions.ts @@ -1,4 +1,4 @@ import { gql } from "graphql-tag"; // Place fields alphabetically to ensure easier lookup and navigation. -export const unions = gql``; +// export const unions = gql``; diff --git a/src/types/generatedGraphQLTypes.ts b/src/types/generatedGraphQLTypes.ts index 45841246ebc..f3421b206c9 100644 --- a/src/types/generatedGraphQLTypes.ts +++ b/src/types/generatedGraphQLTypes.ts @@ -7,11 +7,10 @@ import type { InterfaceAdvertisement as InterfaceAdvertisementModel } from '../m import type { InterfaceAgendaItem as InterfaceAgendaItemModel } from '../models/AgendaItem'; import type { InterfaceAgendaSection as InterfaceAgendaSectionModel } from '../models/AgendaSection'; import type { InterfaceCheckIn as InterfaceCheckInModel } from '../models/CheckIn'; -import type { InterfaceMessageChat as InterfaceMessageChatModel } from '../models/MessageChat'; import type { InterfaceComment as InterfaceCommentModel } from '../models/Comment'; import type { InterfaceCommunity as InterfaceCommunityModel } from '../models/Community'; -import type { InterfaceDirectChat as InterfaceDirectChatModel } from '../models/DirectChat'; -import type { InterfaceDirectChatMessage as InterfaceDirectChatMessageModel } from '../models/DirectChatMessage'; +import type { InterfaceChat as InterfaceChatModel } from '../models/Chat'; +import type { InterfaceChatMessage as InterfaceChatMessageModel } from '../models/ChatMessage'; import type { InterfaceDonation as InterfaceDonationModel } from '../models/Donation'; import type { InterfaceEvent as InterfaceEventModel } from '../models/Event'; import type { InterfaceEventAttendee as InterfaceEventAttendeeModel } from '../models/EventAttendee'; @@ -23,8 +22,6 @@ import type { InterfaceFund as InterfaceFundModel } from '../models/Fund'; import type { InterfaceFundraisingCampaign as InterfaceFundraisingCampaignModel } from '../models/FundraisingCampaign'; import type { InterfaceFundraisingCampaignPledges as InterfaceFundraisingCampaignPledgesModel } from '../models/FundraisingCampaignPledge'; import type { InterfaceGroup as InterfaceGroupModel } from '../models/Group'; -import type { InterfaceGroupChat as InterfaceGroupChatModel } from '../models/GroupChat'; -import type { InterfaceGroupChatMessage as InterfaceGroupChatMessageModel } from '../models/GroupChatMessage'; import type { InterfaceLanguage as InterfaceLanguageModel } from '../models/Language'; import type { InterfaceMembershipRequest as InterfaceMembershipRequestModel } from '../models/MembershipRequest'; import type { InterfaceMessage as InterfaceMessageModel } from '../models/Message'; @@ -37,6 +34,7 @@ import type { InterfaceRecurrenceRule as InterfaceRecurrenceRuleModel } from '.. import type { InterfaceOrganizationTagUser as InterfaceOrganizationTagUserModel } from '../models/OrganizationTagUser'; import type { InterfaceUser as InterfaceUserModel } from '../models/User'; import type { InterfaceVenue as InterfaceVenueModel } from '../models/Venue'; +import type { InterfaceVolunteerMembership as InterfaceVolunteerMembershipModel } from '../models/VolunteerMembership'; export type Maybe = T | null; export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; @@ -72,8 +70,11 @@ export type ActionItem = { __typename?: 'ActionItem'; _id: Scalars['ID']['output']; actionItemCategory?: Maybe; - allotedHours?: Maybe; - assignee?: Maybe; + allottedHours?: Maybe; + assignee?: Maybe; + assigneeGroup?: Maybe; + assigneeType: Scalars['String']['output']; + assigneeUser?: Maybe; assigner?: Maybe; assignmentDate: Scalars['Date']['output']; completionDate: Scalars['Date']['output']; @@ -109,6 +110,7 @@ export type ActionItemWhereInput = { categoryName?: InputMaybe; event_id?: InputMaybe; is_completed?: InputMaybe; + orgId?: InputMaybe; }; export type ActionItemsOrderByInput = @@ -117,6 +119,11 @@ export type ActionItemsOrderByInput = | 'dueDate_ASC' | 'dueDate_DESC'; +export type AddPeopleToUserTagInput = { + tagId: Scalars['ID']['input']; + userIds: Array; +}; + export type Address = { __typename?: 'Address'; city?: Maybe; @@ -264,6 +271,34 @@ export type CampaignWhereInput = { organizationId?: InputMaybe; }; +export type Chat = { + __typename?: 'Chat'; + _id: Scalars['ID']['output']; + admins?: Maybe>>; + createdAt: Scalars['DateTime']['output']; + creator?: Maybe; + image?: Maybe; + isGroup: Scalars['Boolean']['output']; + lastMessageId?: Maybe; + messages?: Maybe>>; + name?: Maybe; + organization?: Maybe; + updatedAt: Scalars['DateTime']['output']; + users: Array; +}; + +export type ChatMessage = { + __typename?: 'ChatMessage'; + _id: Scalars['ID']['output']; + chatMessageBelongsTo: Chat; + createdAt: Scalars['DateTime']['output']; + deletedBy?: Maybe>>; + messageContent: Scalars['String']['output']; + replyTo?: Maybe; + sender: User; + updatedAt: Scalars['DateTime']['output']; +}; + export type CheckIn = { __typename?: 'CheckIn'; _id: Scalars['ID']['output']; @@ -318,6 +353,7 @@ export type Community = { logoUrl?: Maybe; name: Scalars['String']['output']; socialMediaUrls?: Maybe; + timeout?: Maybe; websiteLink?: Maybe; }; @@ -352,8 +388,9 @@ export type ConnectionPageInfo = { }; export type CreateActionItemInput = { - allotedHours?: InputMaybe; + allottedHours?: InputMaybe; assigneeId: Scalars['ID']['input']; + assigneeType: Scalars['String']['input']; dueDate?: InputMaybe; eventId?: InputMaybe; preCompletionNotes?: InputMaybe; @@ -415,8 +452,6 @@ export type CreateCommentPayload = { userErrors: Array; }; -export type CreateDirectChatError = OrganizationNotFoundError | UserNotFoundError; - export type CreateMemberError = MemberNotFoundError | OrganizationNotFoundError | UserNotAuthorizedAdminError | UserNotAuthorizedError | UserNotFoundError; export type CreateMemberPayload = { @@ -625,28 +660,6 @@ export type DeletePayload = { success: Scalars['Boolean']['output']; }; -export type DirectChat = { - __typename?: 'DirectChat'; - _id: Scalars['ID']['output']; - createdAt: Scalars['DateTime']['output']; - creator?: Maybe; - messages?: Maybe>>; - organization?: Maybe; - updatedAt: Scalars['DateTime']['output']; - users: Array; -}; - -export type DirectChatMessage = { - __typename?: 'DirectChatMessage'; - _id: Scalars['ID']['output']; - createdAt: Scalars['DateTime']['output']; - directChatMessageBelongsTo: DirectChat; - messageContent: Scalars['String']['output']; - receiver: User; - sender: User; - updatedAt: Scalars['DateTime']['output']; -}; - export type Donation = { __typename?: 'Donation'; _id: Scalars['ID']['output']; @@ -741,6 +754,8 @@ export type Event = { startTime?: Maybe; title: Scalars['String']['output']; updatedAt: Scalars['DateTime']['output']; + volunteerGroups?: Maybe>>; + volunteers?: Maybe>>; }; @@ -811,13 +826,15 @@ export type EventOrderByInput = export type EventVolunteer = { __typename?: 'EventVolunteer'; _id: Scalars['ID']['output']; + assignments?: Maybe>>; createdAt: Scalars['DateTime']['output']; creator?: Maybe; event?: Maybe; - group?: Maybe; - isAssigned?: Maybe; - isInvited?: Maybe; - response?: Maybe; + groups?: Maybe>>; + hasAccepted: Scalars['Boolean']['output']; + hoursHistory?: Maybe>>; + hoursVolunteered: Scalars['Float']['output']; + isPublic: Scalars['Boolean']['output']; updatedAt: Scalars['DateTime']['output']; user: User; }; @@ -825,8 +842,10 @@ export type EventVolunteer = { export type EventVolunteerGroup = { __typename?: 'EventVolunteerGroup'; _id: Scalars['ID']['output']; + assignments?: Maybe>>; createdAt: Scalars['DateTime']['output']; creator?: Maybe; + description?: Maybe; event?: Maybe; leader: User; name?: Maybe; @@ -836,20 +855,31 @@ export type EventVolunteerGroup = { }; export type EventVolunteerGroupInput = { + description?: InputMaybe; eventId: Scalars['ID']['input']; - name?: InputMaybe; + leaderId: Scalars['ID']['input']; + name: Scalars['String']['input']; + volunteerUserIds: Array; volunteersRequired?: InputMaybe; }; +export type EventVolunteerGroupOrderByInput = + | 'assignments_ASC' + | 'assignments_DESC' + | 'volunteers_ASC' + | 'volunteers_DESC'; + export type EventVolunteerGroupWhereInput = { eventId?: InputMaybe; + leaderName?: InputMaybe; name_contains?: InputMaybe; - volunteerId?: InputMaybe; + orgId?: InputMaybe; + userId?: InputMaybe; }; export type EventVolunteerInput = { eventId: Scalars['ID']['input']; - groupId: Scalars['ID']['input']; + groupId?: InputMaybe; userId: Scalars['ID']['input']; }; @@ -857,6 +887,18 @@ export type EventVolunteerResponse = | 'NO' | 'YES'; +export type EventVolunteerWhereInput = { + eventId?: InputMaybe; + groupId?: InputMaybe; + hasAccepted?: InputMaybe; + id?: InputMaybe; + name_contains?: InputMaybe; +}; + +export type EventVolunteersOrderByInput = + | 'hoursVolunteered_ASC' + | 'hoursVolunteered_DESC'; + export type EventWhereInput = { description?: InputMaybe; description_contains?: InputMaybe; @@ -912,6 +954,36 @@ export type FieldError = { path: Array; }; +export type File = { + __typename?: 'File'; + _id: Scalars['ID']['output']; + archived: Scalars['Boolean']['output']; + archivedAt?: Maybe; + backupStatus: Scalars['String']['output']; + createdAt: Scalars['DateTime']['output']; + encryption: Scalars['Boolean']['output']; + fileName: Scalars['String']['output']; + hash: Hash; + metadata: FileMetadata; + mimeType: Scalars['String']['output']; + referenceCount: Scalars['Int']['output']; + size: Scalars['Int']['output']; + status: Status; + updatedAt: Scalars['DateTime']['output']; + uri: Scalars['String']['output']; + visibility: FileVisibility; +}; + +export type FileMetadata = { + __typename?: 'FileMetadata'; + bucketName: Scalars['String']['output']; + objectKey: Scalars['String']['output']; +}; + +export type FileVisibility = + | 'PRIVATE' + | 'PUBLIC'; + export type ForgotPasswordData = { newPassword: Scalars['String']['input']; otpToken: Scalars['String']['input']; @@ -1017,26 +1089,16 @@ export type Group = { updatedAt: Scalars['DateTime']['output']; }; -export type GroupChat = { - __typename?: 'GroupChat'; - _id: Scalars['ID']['output']; - createdAt: Scalars['DateTime']['output']; - creator?: Maybe; - messages?: Maybe>>; - organization: Organization; - title: Scalars['String']['output']; - updatedAt: Scalars['DateTime']['output']; - users: Array; +export type Hash = { + __typename?: 'Hash'; + algorithm: Scalars['String']['output']; + value: Scalars['String']['output']; }; -export type GroupChatMessage = { - __typename?: 'GroupChatMessage'; - _id: Scalars['ID']['output']; - createdAt: Scalars['DateTime']['output']; - groupChatMessageBelongsTo: GroupChat; - messageContent: Scalars['String']['output']; - sender: User; - updatedAt: Scalars['DateTime']['output']; +export type HoursHistory = { + __typename?: 'HoursHistory'; + date: Scalars['Date']['output']; + hours: Scalars['Float']['output']; }; export type InvalidCursor = FieldError & { @@ -1111,6 +1173,10 @@ export type MembershipRequest = { }; export type MembershipRequestsWhereInput = { + creatorId?: InputMaybe; + creatorId_in?: InputMaybe>; + creatorId_not?: InputMaybe; + creatorId_not_in?: InputMaybe>; id?: InputMaybe; id_contains?: InputMaybe; id_in?: InputMaybe>; @@ -1131,22 +1197,6 @@ export type Message = { videoUrl?: Maybe; }; -export type MessageChat = { - __typename?: 'MessageChat'; - _id: Scalars['ID']['output']; - createdAt: Scalars['DateTime']['output']; - languageBarrier?: Maybe; - message: Scalars['String']['output']; - receiver: User; - sender: User; - updatedAt: Scalars['DateTime']['output']; -}; - -export type MessageChatInput = { - message: Scalars['String']['input']; - receiver: Scalars['ID']['input']; -}; - export type MinimumLengthError = FieldError & { __typename?: 'MinimumLengthError'; limit: Scalars['Int']['output']; @@ -1168,12 +1218,12 @@ export type Mutation = { addLanguageTranslation: Language; addOrganizationCustomField: OrganizationCustomField; addOrganizationImage: Organization; + addPeopleToUserTag?: Maybe; addPledgeToFundraisingCampaign: FundraisingCampaignPledge; addUserCustomData: UserCustomData; addUserImage: User; - addUserToGroupChat: GroupChat; addUserToUserFamily: UserFamily; - adminRemoveGroup: GroupChat; + assignToUserTags?: Maybe; assignUserTag?: Maybe; blockPluginCreationBySuperadmin: AppUserProfile; blockUser: User; @@ -1187,8 +1237,8 @@ export type Mutation = { createAgendaCategory: AgendaCategory; createAgendaItem: AgendaItem; createAgendaSection: AgendaSection; + createChat?: Maybe; createComment?: Maybe; - createDirectChat: DirectChat; createDonation: Donation; createEvent: Event; createEventVolunteer: EventVolunteer; @@ -1196,9 +1246,7 @@ export type Mutation = { createFund: Fund; createFundraisingCampaign: FundraisingCampaign; createFundraisingCampaignPledge: FundraisingCampaignPledge; - createGroupChat: GroupChat; createMember: CreateMemberPayload; - createMessageChat: MessageChat; createNote: Note; createOrganization: Organization; createPlugin: Plugin; @@ -1207,6 +1255,7 @@ export type Mutation = { createUserFamily: UserFamily; createUserTag?: Maybe; createVenue?: Maybe; + createVolunteerMembership: VolunteerMembership; deleteAdvertisement?: Maybe; deleteAgendaCategory: Scalars['ID']['output']; deleteDonationById: DeletePayload; @@ -1233,13 +1282,12 @@ export type Mutation = { removeAgendaItem: AgendaItem; removeAgendaSection: Scalars['ID']['output']; removeComment?: Maybe; - removeDirectChat: DirectChat; removeEvent: Event; removeEventAttendee: User; removeEventVolunteer: EventVolunteer; removeEventVolunteerGroup: EventVolunteerGroup; + removeFromUserTags?: Maybe; removeFundraisingCampaignPledge: FundraisingCampaignPledge; - removeGroupChat: GroupChat; removeMember: Organization; removeOrganization: UserData; removeOrganizationCustomField: OrganizationCustomField; @@ -1248,7 +1296,6 @@ export type Mutation = { removeSampleOrganization: Scalars['Boolean']['output']; removeUserCustomData: UserCustomData; removeUserFamily: UserFamily; - removeUserFromGroupChat: GroupChat; removeUserFromUserFamily: UserFamily; removeUserImage: User; removeUserTag?: Maybe; @@ -1256,8 +1303,7 @@ export type Mutation = { revokeRefreshTokenForUser: Scalars['Boolean']['output']; saveFcmToken: Scalars['Boolean']['output']; sendMembershipRequest: MembershipRequest; - sendMessageToDirectChat: DirectChatMessage; - sendMessageToGroupChat: GroupChatMessage; + sendMessageToChat: ChatMessage; signUp: AuthData; togglePostPin: Post; unassignUserTag?: Maybe; @@ -1283,10 +1329,12 @@ export type Mutation = { updateOrganization: Organization; updatePluginStatus: Plugin; updatePost: Post; + updateSessionTimeout: Scalars['Boolean']['output']; updateUserPassword: UserData; updateUserProfile: User; updateUserRoleInOrganization: Organization; updateUserTag?: Maybe; + updateVolunteerMembership: VolunteerMembership; }; @@ -1323,6 +1371,11 @@ export type MutationAddOrganizationImageArgs = { }; +export type MutationAddPeopleToUserTagArgs = { + input: AddPeopleToUserTagInput; +}; + + export type MutationAddPledgeToFundraisingCampaignArgs = { campaignId: Scalars['ID']['input']; pledgeId: Scalars['ID']['input']; @@ -1341,20 +1394,14 @@ export type MutationAddUserImageArgs = { }; -export type MutationAddUserToGroupChatArgs = { - chatId: Scalars['ID']['input']; - userId: Scalars['ID']['input']; -}; - - export type MutationAddUserToUserFamilyArgs = { familyId: Scalars['ID']['input']; userId: Scalars['ID']['input']; }; -export type MutationAdminRemoveGroupArgs = { - groupId: Scalars['ID']['input']; +export type MutationAssignToUserTagsArgs = { + input: TagActionsInput; }; @@ -1428,14 +1475,14 @@ export type MutationCreateAgendaSectionArgs = { }; -export type MutationCreateCommentArgs = { - data: CommentInput; - postId: Scalars['ID']['input']; +export type MutationCreateChatArgs = { + data: ChatInput; }; -export type MutationCreateDirectChatArgs = { - data: CreateChatInput; +export type MutationCreateCommentArgs = { + data: CommentInput; + postId: Scalars['ID']['input']; }; @@ -1480,21 +1527,11 @@ export type MutationCreateFundraisingCampaignPledgeArgs = { }; -export type MutationCreateGroupChatArgs = { - data: CreateGroupChatInput; -}; - - export type MutationCreateMemberArgs = { input: UserAndOrganizationInput; }; -export type MutationCreateMessageChatArgs = { - data: MessageChatInput; -}; - - export type MutationCreateNoteArgs = { data: NoteInput; }; @@ -1535,6 +1572,11 @@ export type MutationCreateVenueArgs = { }; +export type MutationCreateVolunteerMembershipArgs = { + data: VolunteerMembershipInput; +}; + + export type MutationDeleteAdvertisementArgs = { id: Scalars['ID']['input']; }; @@ -1660,12 +1702,6 @@ export type MutationRemoveCommentArgs = { }; -export type MutationRemoveDirectChatArgs = { - chatId: Scalars['ID']['input']; - organizationId: Scalars['ID']['input']; -}; - - export type MutationRemoveEventArgs = { id: Scalars['ID']['input']; recurringEventDeleteType?: InputMaybe; @@ -1687,13 +1723,13 @@ export type MutationRemoveEventVolunteerGroupArgs = { }; -export type MutationRemoveFundraisingCampaignPledgeArgs = { - id: Scalars['ID']['input']; +export type MutationRemoveFromUserTagsArgs = { + input: TagActionsInput; }; -export type MutationRemoveGroupChatArgs = { - chatId: Scalars['ID']['input']; +export type MutationRemoveFundraisingCampaignPledgeArgs = { + id: Scalars['ID']['input']; }; @@ -1733,12 +1769,6 @@ export type MutationRemoveUserFamilyArgs = { }; -export type MutationRemoveUserFromGroupChatArgs = { - chatId: Scalars['ID']['input']; - userId: Scalars['ID']['input']; -}; - - export type MutationRemoveUserFromUserFamilyArgs = { familyId: Scalars['ID']['input']; userId: Scalars['ID']['input']; @@ -1760,15 +1790,10 @@ export type MutationSendMembershipRequestArgs = { }; -export type MutationSendMessageToDirectChatArgs = { - chatId: Scalars['ID']['input']; - messageContent: Scalars['String']['input']; -}; - - -export type MutationSendMessageToGroupChatArgs = { +export type MutationSendMessageToChatArgs = { chatId: Scalars['ID']['input']; messageContent: Scalars['String']['input']; + replyTo?: InputMaybe; }; @@ -1865,7 +1890,7 @@ export type MutationUpdateEventVolunteerArgs = { export type MutationUpdateEventVolunteerGroupArgs = { - data?: InputMaybe; + data: UpdateEventVolunteerGroupInput; id: Scalars['ID']['input']; }; @@ -1918,6 +1943,11 @@ export type MutationUpdatePostArgs = { }; +export type MutationUpdateSessionTimeoutArgs = { + timeout: Scalars['Int']['input']; +}; + + export type MutationUpdateUserPasswordArgs = { data: UpdateUserPasswordInput; }; @@ -1940,6 +1970,12 @@ export type MutationUpdateUserTagArgs = { input: UpdateUserTagInput; }; + +export type MutationUpdateVolunteerMembershipArgs = { + id: Scalars['ID']['input']; + status: Scalars['String']['input']; +}; + export type Note = { __typename?: 'Note'; _id: Scalars['ID']['output']; @@ -2022,6 +2058,8 @@ export type OrganizationUserTagsArgs = { before?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + sortedBy?: InputMaybe; + where?: InputMaybe; }; export type OrganizationCustomField = { @@ -2180,7 +2218,7 @@ export type Post = { comments?: Maybe>>; createdAt: Scalars['DateTime']['output']; creator?: Maybe; - imageUrl?: Maybe; + file?: Maybe; likeCount?: Maybe; likedBy?: Maybe>>; organization: Organization; @@ -2188,7 +2226,6 @@ export type Post = { text: Scalars['String']['output']; title?: Maybe; updatedAt: Scalars['DateTime']['output']; - videoUrl?: Maybe; }; export type PostEdge = { @@ -2270,20 +2307,20 @@ export type Query = { actionItemCategoriesByOrganization?: Maybe>>; actionItemsByEvent?: Maybe>>; actionItemsByOrganization?: Maybe>>; + actionItemsByUser?: Maybe>>; adminPlugin?: Maybe>>; advertisementsConnection?: Maybe; agendaCategory: AgendaCategory; agendaItemByEvent?: Maybe>>; agendaItemByOrganization?: Maybe>>; agendaItemCategoriesByOrganization?: Maybe>>; + chatById: Chat; + chatsByUserId?: Maybe>>; checkAuth: User; customDataByOrganization: Array; customFieldsByOrganization?: Maybe>>; - directChatById?: Maybe; - directChatsByUserID?: Maybe>>; - directChatsMessagesByChatID?: Maybe>>; event?: Maybe; - eventVolunteersByEvent?: Maybe>>; + eventsAttendedByUser?: Maybe>>; eventsByOrganization?: Maybe>>; eventsByOrganizationConnection: Array; fundsByOrganization?: Maybe>>; @@ -2299,18 +2336,19 @@ export type Query = { getEventAttendeesByEventId?: Maybe>>; getEventInvitesByUserId: Array; getEventVolunteerGroups: Array>; + getEventVolunteers: Array>; getFundById: Fund; getFundraisingCampaignPledgeById: FundraisingCampaignPledge; getFundraisingCampaigns: Array>; getNoteById: Note; getPledgesByUserId?: Maybe>>; getPlugins?: Maybe>>; + getRecurringEvents?: Maybe>>; getUserTag?: Maybe; - getUserTagAncestors?: Maybe>>; getVenueByOrgId?: Maybe>>; + getVolunteerMembership: Array>; + getVolunteerRanks: Array>; getlanguage?: Maybe>>; - groupChatById?: Maybe; - groupChatsByUserId?: Maybe>>; hasSubmittedFeedback?: Maybe; isSampleOrganization: Scalars['Boolean']['output']; joinedOrganizations?: Maybe>>; @@ -2351,6 +2389,13 @@ export type QueryActionItemsByOrganizationArgs = { }; +export type QueryActionItemsByUserArgs = { + orderBy?: InputMaybe; + userId: Scalars['ID']['input']; + where?: InputMaybe; +}; + + export type QueryAdminPluginArgs = { orgId: Scalars['ID']['input']; }; @@ -2384,27 +2429,22 @@ export type QueryAgendaItemCategoriesByOrganizationArgs = { }; -export type QueryCustomDataByOrganizationArgs = { - organizationId: Scalars['ID']['input']; -}; - - -export type QueryCustomFieldsByOrganizationArgs = { +export type QueryChatByIdArgs = { id: Scalars['ID']['input']; }; -export type QueryDirectChatByIdArgs = { +export type QueryChatsByUserIdArgs = { id: Scalars['ID']['input']; }; -export type QueryDirectChatsByUserIdArgs = { - id: Scalars['ID']['input']; +export type QueryCustomDataByOrganizationArgs = { + organizationId: Scalars['ID']['input']; }; -export type QueryDirectChatsMessagesByChatIdArgs = { +export type QueryCustomFieldsByOrganizationArgs = { id: Scalars['ID']['input']; }; @@ -2414,8 +2454,9 @@ export type QueryEventArgs = { }; -export type QueryEventVolunteersByEventArgs = { - id: Scalars['ID']['input']; +export type QueryEventsAttendedByUserArgs = { + id?: InputMaybe; + orderBy?: InputMaybe; }; @@ -2429,6 +2470,7 @@ export type QueryEventsByOrganizationConnectionArgs = { first?: InputMaybe; orderBy?: InputMaybe; skip?: InputMaybe; + upcomingOnly?: InputMaybe; where?: InputMaybe; }; @@ -2490,7 +2532,14 @@ export type QueryGetEventInvitesByUserIdArgs = { export type QueryGetEventVolunteerGroupsArgs = { - where?: InputMaybe; + orderBy?: InputMaybe; + where: EventVolunteerGroupWhereInput; +}; + + +export type QueryGetEventVolunteersArgs = { + orderBy?: InputMaybe; + where: EventVolunteerWhereInput; }; @@ -2525,12 +2574,12 @@ export type QueryGetPledgesByUserIdArgs = { }; -export type QueryGetUserTagArgs = { - id: Scalars['ID']['input']; +export type QueryGetRecurringEventsArgs = { + baseRecurringEventId: Scalars['ID']['input']; }; -export type QueryGetUserTagAncestorsArgs = { +export type QueryGetUserTagArgs = { id: Scalars['ID']['input']; }; @@ -2544,18 +2593,20 @@ export type QueryGetVenueByOrgIdArgs = { }; -export type QueryGetlanguageArgs = { - lang_code: Scalars['String']['input']; +export type QueryGetVolunteerMembershipArgs = { + orderBy?: InputMaybe; + where: VolunteerMembershipWhereInput; }; -export type QueryGroupChatByIdArgs = { - id: Scalars['ID']['input']; +export type QueryGetVolunteerRanksArgs = { + orgId: Scalars['ID']['input']; + where: VolunteerRankWhereInput; }; -export type QueryGroupChatsByUserIdArgs = { - id: Scalars['ID']['input']; +export type QueryGetlanguageArgs = { + lang_code: Scalars['String']['input']; }; @@ -2732,20 +2783,18 @@ export type Status = export type Subscription = { __typename?: 'Subscription'; - directMessageChat?: Maybe; - messageSentToDirectChat?: Maybe; - messageSentToGroupChat?: Maybe; + messageSentToChat?: Maybe; onPluginUpdate?: Maybe; }; -export type SubscriptionMessageSentToDirectChatArgs = { +export type SubscriptionMessageSentToChatArgs = { userId: Scalars['ID']['input']; }; - -export type SubscriptionMessageSentToGroupChatArgs = { - userId: Scalars['ID']['input']; +export type TagActionsInput = { + currentTagId: Scalars['ID']['input']; + selectedTagIds: Array; }; export type ToggleUserTagAssignInput = { @@ -2781,8 +2830,9 @@ export type UpdateActionItemCategoryInput = { }; export type UpdateActionItemInput = { - allotedHours?: InputMaybe; + allottedHours?: InputMaybe; assigneeId?: InputMaybe; + assigneeType?: InputMaybe; completionDate?: InputMaybe; dueDate?: InputMaybe; isCompleted?: InputMaybe; @@ -2853,16 +2903,16 @@ export type UpdateEventInput = { }; export type UpdateEventVolunteerGroupInput = { - eventId?: InputMaybe; + description?: InputMaybe; + eventId: Scalars['ID']['input']; name?: InputMaybe; volunteersRequired?: InputMaybe; }; export type UpdateEventVolunteerInput = { - eventId?: InputMaybe; - isAssigned?: InputMaybe; - isInvited?: InputMaybe; - response?: InputMaybe; + assignments?: InputMaybe>>; + hasAccepted?: InputMaybe; + isPublic?: InputMaybe; }; export type UpdateFundCampaignInput = { @@ -2939,8 +2989,11 @@ export type User = { email: Scalars['EmailAddress']['output']; employmentStatus?: Maybe; eventAdmin?: Maybe>>; + eventsAttended?: Maybe>>; + file?: Maybe; firstName: Scalars['String']['output']; gender?: Maybe; + identifier: Scalars['Int']['output']; image?: Maybe; joinedOrganizations?: Maybe>>; lastName: Scalars['String']['output']; @@ -3016,6 +3069,10 @@ export type UserInput = { selectedOrganization: Scalars['ID']['input']; }; +export type UserNameWhereInput = { + starts_with: Scalars['String']['input']; +}; + export type UserNotAuthorizedAdminError = Error & { __typename?: 'UserNotAuthorizedAdminError'; message: Scalars['String']['output']; @@ -3060,6 +3117,8 @@ export type UserTag = { __typename?: 'UserTag'; /** A field to get the mongodb object id identifier for this UserTag. */ _id: Scalars['ID']['output']; + /** A field to traverse the ancestor tags of this UserTag. */ + ancestorTags?: Maybe>>; /** * A connection field to traverse a list of UserTag this UserTag is a * parent to. @@ -3076,6 +3135,11 @@ export type UserTag = { * to. */ usersAssignedTo?: Maybe; + /** + * A connection field to traverse a list of Users this UserTag is not assigned + * to, to see and select among them and assign this tag. + */ + usersToAssignTo?: Maybe; }; @@ -3084,6 +3148,8 @@ export type UserTagChildTagsArgs = { before?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + sortedBy?: InputMaybe; + where?: InputMaybe; }; @@ -3092,6 +3158,43 @@ export type UserTagUsersAssignedToArgs = { before?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + sortedBy?: InputMaybe; + where?: InputMaybe; +}; + + +export type UserTagUsersToAssignToArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + where?: InputMaybe; +}; + +export type UserTagNameWhereInput = { + starts_with: Scalars['String']['input']; +}; + +export type UserTagSortedByInput = { + id: SortedByOrder; +}; + +export type UserTagUsersAssignedToSortedByInput = { + id: SortedByOrder; +}; + +export type UserTagUsersAssignedToWhereInput = { + firstName?: InputMaybe; + lastName?: InputMaybe; +}; + +export type UserTagUsersToAssignToWhereInput = { + firstName?: InputMaybe; + lastName?: InputMaybe; +}; + +export type UserTagWhereInput = { + name?: InputMaybe; }; /** A default connection on the UserTag type. */ @@ -3187,6 +3290,54 @@ export type VenueWhereInput = { name_starts_with?: InputMaybe; }; +export type VolunteerMembership = { + __typename?: 'VolunteerMembership'; + _id: Scalars['ID']['output']; + createdAt: Scalars['DateTime']['output']; + createdBy?: Maybe; + event: Event; + group?: Maybe; + status: Scalars['String']['output']; + updatedAt: Scalars['DateTime']['output']; + updatedBy?: Maybe; + volunteer: EventVolunteer; +}; + +export type VolunteerMembershipInput = { + event: Scalars['ID']['input']; + group?: InputMaybe; + status: Scalars['String']['input']; + userId: Scalars['ID']['input']; +}; + +export type VolunteerMembershipOrderByInput = + | 'createdAt_ASC' + | 'createdAt_DESC'; + +export type VolunteerMembershipWhereInput = { + eventId?: InputMaybe; + eventTitle?: InputMaybe; + filter?: InputMaybe; + groupId?: InputMaybe; + status?: InputMaybe; + userId?: InputMaybe; + userName?: InputMaybe; +}; + +export type VolunteerRank = { + __typename?: 'VolunteerRank'; + hoursVolunteered: Scalars['Float']['output']; + rank: Scalars['Int']['output']; + user: User; +}; + +export type VolunteerRankWhereInput = { + limit?: InputMaybe; + nameContains?: InputMaybe; + orderBy: Scalars['String']['input']; + timeFrame: Scalars['String']['input']; +}; + export type WeekDays = | 'FRIDAY' | 'MONDAY' @@ -3196,23 +3347,14 @@ export type WeekDays = | 'TUESDAY' | 'WEDNESDAY'; -export type CreateChatInput = { +export type ChatInput = { + image?: InputMaybe; + isGroup: Scalars['Boolean']['input']; + name?: InputMaybe; organizationId?: InputMaybe; userIds: Array; }; -export type CreateDirectChatPayload = { - __typename?: 'createDirectChatPayload'; - directChat?: Maybe; - userErrors: Array; -}; - -export type CreateGroupChatInput = { - organizationId: Scalars['ID']['input']; - title: Scalars['String']['input']; - userIds: Array; -}; - export type CreateUserFamilyInput = { title: Scalars['String']['input']; userIds: Array; @@ -3286,7 +3428,6 @@ export type ResolversUnionTypes<_RefType extends Record> = { ConnectionError: ( InvalidCursor ) | ( MaximumValueError ); CreateAdminError: ( OrganizationMemberNotFoundError ) | ( OrganizationNotFoundError ) | ( UserNotAuthorizedError ) | ( UserNotFoundError ); CreateCommentError: ( PostNotFoundError ); - CreateDirectChatError: ( OrganizationNotFoundError ) | ( UserNotFoundError ); CreateMemberError: ( MemberNotFoundError ) | ( OrganizationNotFoundError ) | ( UserNotAuthorizedAdminError ) | ( UserNotAuthorizedError ) | ( UserNotFoundError ); }; @@ -3304,6 +3445,7 @@ export type ResolversTypes = { ActionItemCategoryWhereInput: ActionItemCategoryWhereInput; ActionItemWhereInput: ActionItemWhereInput; ActionItemsOrderByInput: ActionItemsOrderByInput; + AddPeopleToUserTagInput: AddPeopleToUserTagInput; Address: ResolverTypeWrapper
; AddressInput: AddressInput; Advertisement: ResolverTypeWrapper; @@ -3321,6 +3463,8 @@ export type ResolversTypes = { Boolean: ResolverTypeWrapper; CampaignOrderByInput: CampaignOrderByInput; CampaignWhereInput: CampaignWhereInput; + Chat: ResolverTypeWrapper; + ChatMessage: ResolverTypeWrapper; CheckIn: ResolverTypeWrapper; CheckInCheckOutInput: CheckInCheckOutInput; CheckInStatus: ResolverTypeWrapper & { checkIn?: Maybe, user: ResolversTypes['User'] }>; @@ -3341,7 +3485,6 @@ export type ResolversTypes = { CreateAgendaSectionInput: CreateAgendaSectionInput; CreateCommentError: ResolverTypeWrapper['CreateCommentError']>; CreateCommentPayload: ResolverTypeWrapper & { comment?: Maybe, userErrors: Array }>; - CreateDirectChatError: ResolverTypeWrapper['CreateDirectChatError']>; CreateMemberError: ResolverTypeWrapper['CreateMemberError']>; CreateMemberPayload: ResolverTypeWrapper & { organization?: Maybe, userErrors: Array }>; CreateUserTagInput: CreateUserTagInput; @@ -3352,8 +3495,6 @@ export type ResolversTypes = { DefaultConnectionPageInfo: ResolverTypeWrapper; DeleteAdvertisementPayload: ResolverTypeWrapper & { advertisement?: Maybe }>; DeletePayload: ResolverTypeWrapper; - DirectChat: ResolverTypeWrapper; - DirectChatMessage: ResolverTypeWrapper; Donation: ResolverTypeWrapper; DonationWhereInput: DonationWhereInput; EditVenueInput: EditVenueInput; @@ -3369,14 +3510,20 @@ export type ResolversTypes = { EventVolunteer: ResolverTypeWrapper; EventVolunteerGroup: ResolverTypeWrapper; EventVolunteerGroupInput: EventVolunteerGroupInput; + EventVolunteerGroupOrderByInput: EventVolunteerGroupOrderByInput; EventVolunteerGroupWhereInput: EventVolunteerGroupWhereInput; EventVolunteerInput: EventVolunteerInput; EventVolunteerResponse: EventVolunteerResponse; + EventVolunteerWhereInput: EventVolunteerWhereInput; + EventVolunteersOrderByInput: EventVolunteersOrderByInput; EventWhereInput: EventWhereInput; ExtendSession: ResolverTypeWrapper; Feedback: ResolverTypeWrapper; FeedbackInput: FeedbackInput; FieldError: ResolverTypeWrapper['FieldError']>; + File: ResolverTypeWrapper; + FileMetadata: ResolverTypeWrapper; + FileVisibility: FileVisibility; Float: ResolverTypeWrapper; ForgotPasswordData: ForgotPasswordData; Frequency: Frequency; @@ -3390,8 +3537,8 @@ export type ResolversTypes = { FundraisingCampaignPledge: ResolverTypeWrapper; Gender: Gender; Group: ResolverTypeWrapper; - GroupChat: ResolverTypeWrapper; - GroupChatMessage: ResolverTypeWrapper; + Hash: ResolverTypeWrapper; + HoursHistory: ResolverTypeWrapper; ID: ResolverTypeWrapper; Int: ResolverTypeWrapper; InvalidCursor: ResolverTypeWrapper; @@ -3410,8 +3557,6 @@ export type ResolversTypes = { MembershipRequest: ResolverTypeWrapper; MembershipRequestsWhereInput: MembershipRequestsWhereInput; Message: ResolverTypeWrapper; - MessageChat: ResolverTypeWrapper; - MessageChatInput: MessageChatInput; MinimumLengthError: ResolverTypeWrapper; MinimumValueError: ResolverTypeWrapper; Mutation: ResolverTypeWrapper<{}>; @@ -3456,6 +3601,7 @@ export type ResolversTypes = { Status: Status; String: ResolverTypeWrapper; Subscription: ResolverTypeWrapper<{}>; + TagActionsInput: TagActionsInput; Time: ResolverTypeWrapper; ToggleUserTagAssignInput: ToggleUserTagAssignInput; Translation: ResolverTypeWrapper; @@ -3490,6 +3636,7 @@ export type ResolversTypes = { UserData: ResolverTypeWrapper & { appUserProfile?: Maybe, user: ResolversTypes['User'] }>; UserFamily: ResolverTypeWrapper; UserInput: UserInput; + UserNameWhereInput: UserNameWhereInput; UserNotAuthorizedAdminError: ResolverTypeWrapper; UserNotAuthorizedError: ResolverTypeWrapper; UserNotFoundError: ResolverTypeWrapper; @@ -3497,6 +3644,12 @@ export type ResolversTypes = { UserPhone: ResolverTypeWrapper; UserPhoneInput: UserPhoneInput; UserTag: ResolverTypeWrapper; + UserTagNameWhereInput: UserTagNameWhereInput; + UserTagSortedByInput: UserTagSortedByInput; + UserTagUsersAssignedToSortedByInput: UserTagUsersAssignedToSortedByInput; + UserTagUsersAssignedToWhereInput: UserTagUsersAssignedToWhereInput; + UserTagUsersToAssignToWhereInput: UserTagUsersToAssignToWhereInput; + UserTagWhereInput: UserTagWhereInput; UserTagsConnection: ResolverTypeWrapper & { edges: Array }>; UserTagsConnectionEdge: ResolverTypeWrapper & { node: ResolversTypes['UserTag'] }>; UserType: UserType; @@ -3507,10 +3660,14 @@ export type ResolversTypes = { VenueInput: VenueInput; VenueOrderByInput: VenueOrderByInput; VenueWhereInput: VenueWhereInput; + VolunteerMembership: ResolverTypeWrapper; + VolunteerMembershipInput: VolunteerMembershipInput; + VolunteerMembershipOrderByInput: VolunteerMembershipOrderByInput; + VolunteerMembershipWhereInput: VolunteerMembershipWhereInput; + VolunteerRank: ResolverTypeWrapper & { user: ResolversTypes['User'] }>; + VolunteerRankWhereInput: VolunteerRankWhereInput; WeekDays: WeekDays; - createChatInput: CreateChatInput; - createDirectChatPayload: ResolverTypeWrapper & { directChat?: Maybe, userErrors: Array }>; - createGroupChatInput: CreateGroupChatInput; + chatInput: ChatInput; createUserFamilyInput: CreateUserFamilyInput; }; @@ -3520,6 +3677,7 @@ export type ResolversParentTypes = { ActionItemCategory: InterfaceActionItemCategoryModel; ActionItemCategoryWhereInput: ActionItemCategoryWhereInput; ActionItemWhereInput: ActionItemWhereInput; + AddPeopleToUserTagInput: AddPeopleToUserTagInput; Address: Address; AddressInput: AddressInput; Advertisement: InterfaceAdvertisementModel; @@ -3535,6 +3693,8 @@ export type ResolversParentTypes = { AuthData: Omit & { appUserProfile: ResolversParentTypes['AppUserProfile'], user: ResolversParentTypes['User'] }; Boolean: Scalars['Boolean']['output']; CampaignWhereInput: CampaignWhereInput; + Chat: InterfaceChatModel; + ChatMessage: InterfaceChatMessageModel; CheckIn: InterfaceCheckInModel; CheckInCheckOutInput: CheckInCheckOutInput; CheckInStatus: Omit & { checkIn?: Maybe, user: ResolversParentTypes['User'] }; @@ -3555,7 +3715,6 @@ export type ResolversParentTypes = { CreateAgendaSectionInput: CreateAgendaSectionInput; CreateCommentError: ResolversUnionTypes['CreateCommentError']; CreateCommentPayload: Omit & { comment?: Maybe, userErrors: Array }; - CreateDirectChatError: ResolversUnionTypes['CreateDirectChatError']; CreateMemberError: ResolversUnionTypes['CreateMemberError']; CreateMemberPayload: Omit & { organization?: Maybe, userErrors: Array }; CreateUserTagInput: CreateUserTagInput; @@ -3565,8 +3724,6 @@ export type ResolversParentTypes = { DefaultConnectionPageInfo: DefaultConnectionPageInfo; DeleteAdvertisementPayload: Omit & { advertisement?: Maybe }; DeletePayload: DeletePayload; - DirectChat: InterfaceDirectChatModel; - DirectChatMessage: InterfaceDirectChatMessageModel; Donation: InterfaceDonationModel; DonationWhereInput: DonationWhereInput; EditVenueInput: EditVenueInput; @@ -3581,11 +3738,14 @@ export type ResolversParentTypes = { EventVolunteerGroupInput: EventVolunteerGroupInput; EventVolunteerGroupWhereInput: EventVolunteerGroupWhereInput; EventVolunteerInput: EventVolunteerInput; + EventVolunteerWhereInput: EventVolunteerWhereInput; EventWhereInput: EventWhereInput; ExtendSession: ExtendSession; Feedback: InterfaceFeedbackModel; FeedbackInput: FeedbackInput; FieldError: ResolversInterfaceTypes['FieldError']; + File: File; + FileMetadata: FileMetadata; Float: Scalars['Float']['output']; ForgotPasswordData: ForgotPasswordData; Fund: InterfaceFundModel; @@ -3596,8 +3756,8 @@ export type ResolversParentTypes = { FundraisingCampaign: InterfaceFundraisingCampaignModel; FundraisingCampaignPledge: InterfaceFundraisingCampaignPledgesModel; Group: InterfaceGroupModel; - GroupChat: InterfaceGroupChatModel; - GroupChatMessage: InterfaceGroupChatMessageModel; + Hash: Hash; + HoursHistory: HoursHistory; ID: Scalars['ID']['output']; Int: Scalars['Int']['output']; InvalidCursor: InvalidCursor; @@ -3614,8 +3774,6 @@ export type ResolversParentTypes = { MembershipRequest: InterfaceMembershipRequestModel; MembershipRequestsWhereInput: MembershipRequestsWhereInput; Message: InterfaceMessageModel; - MessageChat: InterfaceMessageChatModel; - MessageChatInput: MessageChatInput; MinimumLengthError: MinimumLengthError; MinimumValueError: MinimumValueError; Mutation: {}; @@ -3653,6 +3811,7 @@ export type ResolversParentTypes = { SocialMediaUrlsInput: SocialMediaUrlsInput; String: Scalars['String']['output']; Subscription: {}; + TagActionsInput: TagActionsInput; Time: Scalars['Time']['output']; ToggleUserTagAssignInput: ToggleUserTagAssignInput; Translation: Translation; @@ -3686,12 +3845,19 @@ export type ResolversParentTypes = { UserData: Omit & { appUserProfile?: Maybe, user: ResolversParentTypes['User'] }; UserFamily: InterfaceUserFamilyModel; UserInput: UserInput; + UserNameWhereInput: UserNameWhereInput; UserNotAuthorizedAdminError: UserNotAuthorizedAdminError; UserNotAuthorizedError: UserNotAuthorizedError; UserNotFoundError: UserNotFoundError; UserPhone: UserPhone; UserPhoneInput: UserPhoneInput; UserTag: InterfaceOrganizationTagUserModel; + UserTagNameWhereInput: UserTagNameWhereInput; + UserTagSortedByInput: UserTagSortedByInput; + UserTagUsersAssignedToSortedByInput: UserTagUsersAssignedToSortedByInput; + UserTagUsersAssignedToWhereInput: UserTagUsersAssignedToWhereInput; + UserTagUsersToAssignToWhereInput: UserTagUsersToAssignToWhereInput; + UserTagWhereInput: UserTagWhereInput; UserTagsConnection: Omit & { edges: Array }; UserTagsConnectionEdge: Omit & { node: ResolversParentTypes['UserTag'] }; UserWhereInput: UserWhereInput; @@ -3700,9 +3866,12 @@ export type ResolversParentTypes = { Venue: InterfaceVenueModel; VenueInput: VenueInput; VenueWhereInput: VenueWhereInput; - createChatInput: CreateChatInput; - createDirectChatPayload: Omit & { directChat?: Maybe, userErrors: Array }; - createGroupChatInput: CreateGroupChatInput; + VolunteerMembership: InterfaceVolunteerMembershipModel; + VolunteerMembershipInput: VolunteerMembershipInput; + VolunteerMembershipWhereInput: VolunteerMembershipWhereInput; + VolunteerRank: Omit & { user: ResolversParentTypes['User'] }; + VolunteerRankWhereInput: VolunteerRankWhereInput; + chatInput: ChatInput; createUserFamilyInput: CreateUserFamilyInput; }; @@ -3719,8 +3888,11 @@ export type RoleDirectiveResolver = { _id?: Resolver; actionItemCategory?: Resolver, ParentType, ContextType>; - allotedHours?: Resolver, ParentType, ContextType>; - assignee?: Resolver, ParentType, ContextType>; + allottedHours?: Resolver, ParentType, ContextType>; + assignee?: Resolver, ParentType, ContextType>; + assigneeGroup?: Resolver, ParentType, ContextType>; + assigneeType?: Resolver; + assigneeUser?: Resolver, ParentType, ContextType>; assigner?: Resolver, ParentType, ContextType>; assignmentDate?: Resolver; completionDate?: Resolver; @@ -3866,6 +4038,34 @@ export type AuthDataResolvers; }; +export type ChatResolvers = { + _id?: Resolver; + admins?: Resolver>>, ParentType, ContextType>; + createdAt?: Resolver; + creator?: Resolver, ParentType, ContextType>; + image?: Resolver, ParentType, ContextType>; + isGroup?: Resolver; + lastMessageId?: Resolver, ParentType, ContextType>; + messages?: Resolver>>, ParentType, ContextType>; + name?: Resolver, ParentType, ContextType>; + organization?: Resolver, ParentType, ContextType>; + updatedAt?: Resolver; + users?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type ChatMessageResolvers = { + _id?: Resolver; + chatMessageBelongsTo?: Resolver; + createdAt?: Resolver; + deletedBy?: Resolver>>, ParentType, ContextType>; + messageContent?: Resolver; + replyTo?: Resolver, ParentType, ContextType>; + sender?: Resolver; + updatedAt?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type CheckInResolvers = { _id?: Resolver; createdAt?: Resolver; @@ -3910,6 +4110,7 @@ export type CommunityResolvers, ParentType, ContextType>; name?: Resolver; socialMediaUrls?: Resolver, ParentType, ContextType>; + timeout?: Resolver, ParentType, ContextType>; websiteLink?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -3955,10 +4156,6 @@ export type CreateCommentPayloadResolvers; }; -export type CreateDirectChatErrorResolvers = { - __resolveType: TypeResolveFn<'OrganizationNotFoundError' | 'UserNotFoundError', ParentType, ContextType>; -}; - export type CreateMemberErrorResolvers = { __resolveType: TypeResolveFn<'MemberNotFoundError' | 'OrganizationNotFoundError' | 'UserNotAuthorizedAdminError' | 'UserNotAuthorizedError' | 'UserNotFoundError', ParentType, ContextType>; }; @@ -3995,28 +4192,6 @@ export type DeletePayloadResolvers; }; -export type DirectChatResolvers = { - _id?: Resolver; - createdAt?: Resolver; - creator?: Resolver, ParentType, ContextType>; - messages?: Resolver>>, ParentType, ContextType>; - organization?: Resolver, ParentType, ContextType>; - updatedAt?: Resolver; - users?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type DirectChatMessageResolvers = { - _id?: Resolver; - createdAt?: Resolver; - directChatMessageBelongsTo?: Resolver; - messageContent?: Resolver; - receiver?: Resolver; - sender?: Resolver; - updatedAt?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type DonationResolvers = { _id?: Resolver; amount?: Resolver; @@ -4069,6 +4244,8 @@ export type EventResolvers, ParentType, ContextType>; title?: Resolver; updatedAt?: Resolver; + volunteerGroups?: Resolver>>, ParentType, ContextType>; + volunteers?: Resolver>>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -4089,13 +4266,15 @@ export type EventAttendeeResolvers = { _id?: Resolver; + assignments?: Resolver>>, ParentType, ContextType>; createdAt?: Resolver; creator?: Resolver, ParentType, ContextType>; event?: Resolver, ParentType, ContextType>; - group?: Resolver, ParentType, ContextType>; - isAssigned?: Resolver, ParentType, ContextType>; - isInvited?: Resolver, ParentType, ContextType>; - response?: Resolver, ParentType, ContextType>; + groups?: Resolver>>, ParentType, ContextType>; + hasAccepted?: Resolver; + hoursHistory?: Resolver>>, ParentType, ContextType>; + hoursVolunteered?: Resolver; + isPublic?: Resolver; updatedAt?: Resolver; user?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -4103,8 +4282,10 @@ export type EventVolunteerResolvers = { _id?: Resolver; + assignments?: Resolver>>, ParentType, ContextType>; createdAt?: Resolver; creator?: Resolver, ParentType, ContextType>; + description?: Resolver, ParentType, ContextType>; event?: Resolver, ParentType, ContextType>; leader?: Resolver; name?: Resolver, ParentType, ContextType>; @@ -4136,6 +4317,32 @@ export type FieldErrorResolvers, ParentType, ContextType>; }; +export type FileResolvers = { + _id?: Resolver; + archived?: Resolver; + archivedAt?: Resolver, ParentType, ContextType>; + backupStatus?: Resolver; + createdAt?: Resolver; + encryption?: Resolver; + fileName?: Resolver; + hash?: Resolver; + metadata?: Resolver; + mimeType?: Resolver; + referenceCount?: Resolver; + size?: Resolver; + status?: Resolver; + updatedAt?: Resolver; + uri?: Resolver; + visibility?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type FileMetadataResolvers = { + bucketName?: Resolver; + objectKey?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FundResolvers = { _id?: Resolver; campaigns?: Resolver>>, ParentType, ContextType>; @@ -4188,25 +4395,15 @@ export type GroupResolvers; }; -export type GroupChatResolvers = { - _id?: Resolver; - createdAt?: Resolver; - creator?: Resolver, ParentType, ContextType>; - messages?: Resolver>>, ParentType, ContextType>; - organization?: Resolver; - title?: Resolver; - updatedAt?: Resolver; - users?: Resolver, ParentType, ContextType>; +export type HashResolvers = { + algorithm?: Resolver; + value?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; -export type GroupChatMessageResolvers = { - _id?: Resolver; - createdAt?: Resolver; - groupChatMessageBelongsTo?: Resolver; - messageContent?: Resolver; - sender?: Resolver; - updatedAt?: Resolver; +export type HoursHistoryResolvers = { + date?: Resolver; + hours?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; @@ -4281,17 +4478,6 @@ export type MessageResolvers; }; -export type MessageChatResolvers = { - _id?: Resolver; - createdAt?: Resolver; - languageBarrier?: Resolver, ParentType, ContextType>; - message?: Resolver; - receiver?: Resolver; - sender?: Resolver; - updatedAt?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type MinimumLengthErrorResolvers = { limit?: Resolver; message?: Resolver; @@ -4312,12 +4498,12 @@ export type MutationResolvers>; addOrganizationCustomField?: Resolver>; addOrganizationImage?: Resolver>; + addPeopleToUserTag?: Resolver, ParentType, ContextType, RequireFields>; addPledgeToFundraisingCampaign?: Resolver>; addUserCustomData?: Resolver>; addUserImage?: Resolver>; - addUserToGroupChat?: Resolver>; addUserToUserFamily?: Resolver>; - adminRemoveGroup?: Resolver>; + assignToUserTags?: Resolver, ParentType, ContextType, RequireFields>; assignUserTag?: Resolver, ParentType, ContextType, RequireFields>; blockPluginCreationBySuperadmin?: Resolver>; blockUser?: Resolver>; @@ -4331,8 +4517,8 @@ export type MutationResolvers>; createAgendaItem?: Resolver>; createAgendaSection?: Resolver>; + createChat?: Resolver, ParentType, ContextType, RequireFields>; createComment?: Resolver, ParentType, ContextType, RequireFields>; - createDirectChat?: Resolver>; createDonation?: Resolver>; createEvent?: Resolver>; createEventVolunteer?: Resolver>; @@ -4340,9 +4526,7 @@ export type MutationResolvers>; createFundraisingCampaign?: Resolver>; createFundraisingCampaignPledge?: Resolver>; - createGroupChat?: Resolver>; createMember?: Resolver>; - createMessageChat?: Resolver>; createNote?: Resolver>; createOrganization?: Resolver>; createPlugin?: Resolver>; @@ -4351,6 +4535,7 @@ export type MutationResolvers>; createUserTag?: Resolver, ParentType, ContextType, RequireFields>; createVenue?: Resolver, ParentType, ContextType, RequireFields>; + createVolunteerMembership?: Resolver>; deleteAdvertisement?: Resolver, ParentType, ContextType, RequireFields>; deleteAgendaCategory?: Resolver>; deleteDonationById?: Resolver>; @@ -4377,13 +4562,12 @@ export type MutationResolvers>; removeAgendaSection?: Resolver>; removeComment?: Resolver, ParentType, ContextType, RequireFields>; - removeDirectChat?: Resolver>; removeEvent?: Resolver>; removeEventAttendee?: Resolver>; removeEventVolunteer?: Resolver>; removeEventVolunteerGroup?: Resolver>; + removeFromUserTags?: Resolver, ParentType, ContextType, RequireFields>; removeFundraisingCampaignPledge?: Resolver>; - removeGroupChat?: Resolver>; removeMember?: Resolver>; removeOrganization?: Resolver>; removeOrganizationCustomField?: Resolver>; @@ -4392,7 +4576,6 @@ export type MutationResolvers; removeUserCustomData?: Resolver>; removeUserFamily?: Resolver>; - removeUserFromGroupChat?: Resolver>; removeUserFromUserFamily?: Resolver>; removeUserImage?: Resolver; removeUserTag?: Resolver, ParentType, ContextType, RequireFields>; @@ -4400,8 +4583,7 @@ export type MutationResolvers; saveFcmToken?: Resolver>; sendMembershipRequest?: Resolver>; - sendMessageToDirectChat?: Resolver>; - sendMessageToGroupChat?: Resolver>; + sendMessageToChat?: Resolver>; signUp?: Resolver>; togglePostPin?: Resolver>; unassignUserTag?: Resolver, ParentType, ContextType, RequireFields>; @@ -4418,7 +4600,7 @@ export type MutationResolvers>; updateEvent?: Resolver>; updateEventVolunteer?: Resolver>; - updateEventVolunteerGroup?: Resolver>; + updateEventVolunteerGroup?: Resolver>; updateFund?: Resolver>; updateFundraisingCampaign?: Resolver>; updateFundraisingCampaignPledge?: Resolver>; @@ -4427,10 +4609,12 @@ export type MutationResolvers>; updatePluginStatus?: Resolver>; updatePost?: Resolver>; + updateSessionTimeout?: Resolver>; updateUserPassword?: Resolver>; updateUserProfile?: Resolver>; updateUserRoleInOrganization?: Resolver>; updateUserTag?: Resolver, ParentType, ContextType, RequireFields>; + updateVolunteerMembership?: Resolver>; }; export type NoteResolvers = { @@ -4548,7 +4732,7 @@ export type PostResolvers>>, ParentType, ContextType>; createdAt?: Resolver; creator?: Resolver, ParentType, ContextType>; - imageUrl?: Resolver, ParentType, ContextType>; + file?: Resolver, ParentType, ContextType>; likeCount?: Resolver, ParentType, ContextType>; likedBy?: Resolver>>, ParentType, ContextType>; organization?: Resolver; @@ -4556,7 +4740,6 @@ export type PostResolvers; title?: Resolver, ParentType, ContextType>; updatedAt?: Resolver; - videoUrl?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -4582,20 +4765,20 @@ export type QueryResolvers>>, ParentType, ContextType, RequireFields>; actionItemsByEvent?: Resolver>>, ParentType, ContextType, RequireFields>; actionItemsByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; + actionItemsByUser?: Resolver>>, ParentType, ContextType, RequireFields>; adminPlugin?: Resolver>>, ParentType, ContextType, RequireFields>; advertisementsConnection?: Resolver, ParentType, ContextType, Partial>; agendaCategory?: Resolver>; agendaItemByEvent?: Resolver>>, ParentType, ContextType, RequireFields>; agendaItemByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; agendaItemCategoriesByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; + chatById?: Resolver>; + chatsByUserId?: Resolver>>, ParentType, ContextType, RequireFields>; checkAuth?: Resolver; customDataByOrganization?: Resolver, ParentType, ContextType, RequireFields>; customFieldsByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; - directChatById?: Resolver, ParentType, ContextType, RequireFields>; - directChatsByUserID?: Resolver>>, ParentType, ContextType, RequireFields>; - directChatsMessagesByChatID?: Resolver>>, ParentType, ContextType, RequireFields>; event?: Resolver, ParentType, ContextType, RequireFields>; - eventVolunteersByEvent?: Resolver>>, ParentType, ContextType, RequireFields>; + eventsAttendedByUser?: Resolver>>, ParentType, ContextType, Partial>; eventsByOrganization?: Resolver>>, ParentType, ContextType, Partial>; eventsByOrganizationConnection?: Resolver, ParentType, ContextType, Partial>; fundsByOrganization?: Resolver>>, ParentType, ContextType, RequireFields>; @@ -4610,19 +4793,20 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; getEventAttendeesByEventId?: Resolver>>, ParentType, ContextType, RequireFields>; getEventInvitesByUserId?: Resolver, ParentType, ContextType, RequireFields>; - getEventVolunteerGroups?: Resolver>, ParentType, ContextType, Partial>; + getEventVolunteerGroups?: Resolver>, ParentType, ContextType, RequireFields>; + getEventVolunteers?: Resolver>, ParentType, ContextType, RequireFields>; getFundById?: Resolver>; getFundraisingCampaignPledgeById?: Resolver>; getFundraisingCampaigns?: Resolver>, ParentType, ContextType, Partial>; getNoteById?: Resolver>; getPledgesByUserId?: Resolver>>, ParentType, ContextType, RequireFields>; getPlugins?: Resolver>>, ParentType, ContextType>; + getRecurringEvents?: Resolver>>, ParentType, ContextType, RequireFields>; getUserTag?: Resolver, ParentType, ContextType, RequireFields>; - getUserTagAncestors?: Resolver>>, ParentType, ContextType, RequireFields>; getVenueByOrgId?: Resolver>>, ParentType, ContextType, RequireFields>; + getVolunteerMembership?: Resolver>, ParentType, ContextType, RequireFields>; + getVolunteerRanks?: Resolver>, ParentType, ContextType, RequireFields>; getlanguage?: Resolver>>, ParentType, ContextType, RequireFields>; - groupChatById?: Resolver, ParentType, ContextType, RequireFields>; - groupChatsByUserId?: Resolver>>, ParentType, ContextType, RequireFields>; hasSubmittedFeedback?: Resolver, ParentType, ContextType, RequireFields>; isSampleOrganization?: Resolver>; joinedOrganizations?: Resolver>>, ParentType, ContextType, Partial>; @@ -4670,9 +4854,7 @@ export type SocialMediaUrlsResolvers = { - directMessageChat?: SubscriptionResolver, "directMessageChat", ParentType, ContextType>; - messageSentToDirectChat?: SubscriptionResolver, "messageSentToDirectChat", ParentType, ContextType, RequireFields>; - messageSentToGroupChat?: SubscriptionResolver, "messageSentToGroupChat", ParentType, ContextType, RequireFields>; + messageSentToChat?: SubscriptionResolver, "messageSentToChat", ParentType, ContextType, RequireFields>; onPluginUpdate?: SubscriptionResolver, "onPluginUpdate", ParentType, ContextType>; }; @@ -4721,8 +4903,11 @@ export type UserResolvers; employmentStatus?: Resolver, ParentType, ContextType>; eventAdmin?: Resolver>>, ParentType, ContextType>; + eventsAttended?: Resolver>>, ParentType, ContextType>; + file?: Resolver, ParentType, ContextType>; firstName?: Resolver; gender?: Resolver, ParentType, ContextType>; + identifier?: Resolver; image?: Resolver, ParentType, ContextType>; joinedOrganizations?: Resolver>>, ParentType, ContextType>; lastName?: Resolver; @@ -4792,11 +4977,13 @@ export type UserPhoneResolvers = { _id?: Resolver; + ancestorTags?: Resolver>>, ParentType, ContextType>; childTags?: Resolver, ParentType, ContextType, Partial>; name?: Resolver; organization?: Resolver, ParentType, ContextType>; parentTag?: Resolver, ParentType, ContextType>; usersAssignedTo?: Resolver, ParentType, ContextType, Partial>; + usersToAssignTo?: Resolver, ParentType, ContextType, Partial>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -4836,9 +5023,23 @@ export type VenueResolvers; }; -export type CreateDirectChatPayloadResolvers = { - directChat?: Resolver, ParentType, ContextType>; - userErrors?: Resolver, ParentType, ContextType>; +export type VolunteerMembershipResolvers = { + _id?: Resolver; + createdAt?: Resolver; + createdBy?: Resolver, ParentType, ContextType>; + event?: Resolver; + group?: Resolver, ParentType, ContextType>; + status?: Resolver; + updatedAt?: Resolver; + updatedBy?: Resolver, ParentType, ContextType>; + volunteer?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type VolunteerRankResolvers = { + hoursVolunteered?: Resolver; + rank?: Resolver; + user?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; @@ -4857,6 +5058,8 @@ export type Resolvers = { Any?: GraphQLScalarType; AppUserProfile?: AppUserProfileResolvers; AuthData?: AuthDataResolvers; + Chat?: ChatResolvers; + ChatMessage?: ChatMessageResolvers; CheckIn?: CheckInResolvers; CheckInStatus?: CheckInStatusResolvers; CheckOut?: CheckOutResolvers; @@ -4870,7 +5073,6 @@ export type Resolvers = { CreateAdvertisementPayload?: CreateAdvertisementPayloadResolvers; CreateCommentError?: CreateCommentErrorResolvers; CreateCommentPayload?: CreateCommentPayloadResolvers; - CreateDirectChatError?: CreateDirectChatErrorResolvers; CreateMemberError?: CreateMemberErrorResolvers; CreateMemberPayload?: CreateMemberPayloadResolvers; Date?: GraphQLScalarType; @@ -4878,8 +5080,6 @@ export type Resolvers = { DefaultConnectionPageInfo?: DefaultConnectionPageInfoResolvers; DeleteAdvertisementPayload?: DeleteAdvertisementPayloadResolvers; DeletePayload?: DeletePayloadResolvers; - DirectChat?: DirectChatResolvers; - DirectChatMessage?: DirectChatMessageResolvers; Donation?: DonationResolvers; EmailAddress?: GraphQLScalarType; Error?: ErrorResolvers; @@ -4890,12 +5090,14 @@ export type Resolvers = { ExtendSession?: ExtendSessionResolvers; Feedback?: FeedbackResolvers; FieldError?: FieldErrorResolvers; + File?: FileResolvers; + FileMetadata?: FileMetadataResolvers; Fund?: FundResolvers; FundraisingCampaign?: FundraisingCampaignResolvers; FundraisingCampaignPledge?: FundraisingCampaignPledgeResolvers; Group?: GroupResolvers; - GroupChat?: GroupChatResolvers; - GroupChatMessage?: GroupChatMessageResolvers; + Hash?: HashResolvers; + HoursHistory?: HoursHistoryResolvers; InvalidCursor?: InvalidCursorResolvers; JSON?: GraphQLScalarType; Language?: LanguageResolvers; @@ -4907,7 +5109,6 @@ export type Resolvers = { MemberNotFoundError?: MemberNotFoundErrorResolvers; MembershipRequest?: MembershipRequestResolvers; Message?: MessageResolvers; - MessageChat?: MessageChatResolvers; MinimumLengthError?: MinimumLengthErrorResolvers; MinimumValueError?: MinimumValueErrorResolvers; Mutation?: MutationResolvers; @@ -4953,7 +5154,8 @@ export type Resolvers = { UsersConnection?: UsersConnectionResolvers; UsersConnectionEdge?: UsersConnectionEdgeResolvers; Venue?: VenueResolvers; - createDirectChatPayload?: CreateDirectChatPayloadResolvers; + VolunteerMembership?: VolunteerMembershipResolvers; + VolunteerRank?: VolunteerRankResolvers; }; export type DirectiveResolvers = { diff --git a/src/utilities/adminCheck.ts b/src/utilities/adminCheck.ts index 3d49abcd838..8f1b7ac80d4 100644 --- a/src/utilities/adminCheck.ts +++ b/src/utilities/adminCheck.ts @@ -12,12 +12,14 @@ import { AppUserProfile } from "../models"; * This is a utility method. * @param userId - The ID of the current user. It can be a string or a Types.ObjectId. * @param organization - The organization data of `InterfaceOrganization` type. + * @param throwError - A boolean value to determine if the function should throw an error. Default is `true`. * @returns `True` or `False`. */ export const adminCheck = async ( userId: string | Types.ObjectId, organization: InterfaceOrganization, -): Promise => { + throwError: boolean = true, +): Promise => { /** * Check if the user is listed as an admin in the organization. * Compares the user ID with the admin IDs in the organization. @@ -55,10 +57,15 @@ export const adminCheck = async ( * If the user is neither an organization admin nor a super admin, throw an UnauthorizedError. */ if (!userIsOrganizationAdmin && !isUserSuperAdmin) { - throw new errors.UnauthorizedError( - requestContext.translate(`${USER_NOT_AUTHORIZED_ADMIN.MESSAGE}`), - USER_NOT_AUTHORIZED_ADMIN.CODE, - USER_NOT_AUTHORIZED_ADMIN.PARAM, - ); + if (throwError) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ADMIN.MESSAGE), + USER_NOT_AUTHORIZED_ADMIN.CODE, + USER_NOT_AUTHORIZED_ADMIN.PARAM, + ); + } else { + return false; + } } + return true; }; diff --git a/src/utilities/auth.ts b/src/utilities/auth.ts index 84389ac1f4d..9df4712c762 100644 --- a/src/utilities/auth.ts +++ b/src/utilities/auth.ts @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; import { ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET } from "../constants"; import type { InterfaceAppUserProfile, InterfaceUser } from "../models"; -import { User } from "../models"; +import { Community, User } from "../models"; /** * Interface representing the payload of a JWT token. @@ -22,10 +22,17 @@ export interface InterfaceJwtTokenPayload { * @param appUserProfile - Application user profile data * @returns JSON Web Token string payload */ -export const createAccessToken = ( +export const createAccessToken = async ( user: InterfaceUser, appUserProfile: InterfaceAppUserProfile, -): string => { +): Promise => { + let timeout = 30; //in minutes + const community = await Community.findOne().lean(); + + if (community) { + timeout = community.timeout; + } + return jwt.sign( { tokenVersion: appUserProfile.tokenVersion, @@ -33,6 +40,7 @@ export const createAccessToken = ( firstName: user.firstName, lastName: user.lastName, email: user.email, + timeout: timeout, }, ACCESS_TOKEN_SECRET as string, { diff --git a/src/utilities/checks.ts b/src/utilities/checks.ts new file mode 100644 index 00000000000..bac1389537d --- /dev/null +++ b/src/utilities/checks.ts @@ -0,0 +1,153 @@ +import { + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../constants"; +import { errors, requestContext } from "../libraries"; +import type { + InterfaceAppUserProfile, + InterfaceEventVolunteer, + InterfaceEventVolunteerGroup, + InterfaceUser, + InterfaceVolunteerMembership, +} from "../models"; +import { + AppUserProfile, + EventVolunteer, + EventVolunteerGroup, + User, + VolunteerMembership, +} from "../models"; +import { cacheAppUserProfile } from "../services/AppUserProfileCache/cacheAppUserProfile"; +import { findAppUserProfileCache } from "../services/AppUserProfileCache/findAppUserProfileCache"; +import { cacheUsers } from "../services/UserCache/cacheUser"; +import { findUserInCache } from "../services/UserCache/findUserInCache"; + +/** + * This function checks if the user exists. + * @param userId - user id + * @returns User + */ + +export const checkUserExists = async ( + userId: string, +): Promise => { + let currentUser: InterfaceUser | null; + const userFoundInCache = await findUserInCache([userId]); + currentUser = userFoundInCache[0]; + + if (currentUser === null) { + currentUser = await User.findById(userId).lean(); + if (currentUser !== null) await cacheUsers([currentUser]); + } + + if (!currentUser) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM, + ); + } + return currentUser; +}; + +/** + * This function checks if the user has an app profile. + * @param user - user object + * @returns AppUserProfile + */ +export const checkAppUserProfileExists = async ( + user: InterfaceUser, +): Promise => { + let currentUserAppProfile: InterfaceAppUserProfile | null; + const appUserProfileFoundInCache = await findAppUserProfileCache([ + user.appUserProfileId?.toString(), + ]); + currentUserAppProfile = appUserProfileFoundInCache[0]; + if (currentUserAppProfile === null) { + currentUserAppProfile = await AppUserProfile.findOne({ + userId: user._id, + }).lean(); + if (currentUserAppProfile !== null) { + await cacheAppUserProfile([currentUserAppProfile]); + } + } + if (!currentUserAppProfile) { + throw new errors.UnauthorizedError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + return currentUserAppProfile; +}; + +/** + * This function checks if the event volunteer exists. + * @param volunteerId - event volunteer id + * @returns EventVolunteer + */ +export const checkEventVolunteerExists = async ( + volunteerId: string, +): Promise => { + const volunteer = await EventVolunteer.findById(volunteerId); + + if (!volunteer) { + throw new errors.NotFoundError( + requestContext.translate(EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE), + EVENT_VOLUNTEER_NOT_FOUND_ERROR.CODE, + EVENT_VOLUNTEER_NOT_FOUND_ERROR.PARAM, + ); + } + + return volunteer; +}; + +/** + * This function checks if the volunteer group exists. + * @param groupId - event volunteer group id + * @returns EventVolunteerGroup + */ + +export const checkVolunteerGroupExists = async ( + groupId: string, +): Promise => { + const volunteerGroup = await EventVolunteerGroup.findOne({ + _id: groupId, + }); + + if (!volunteerGroup) { + throw new errors.NotFoundError( + requestContext.translate(EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE), + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.CODE, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.PARAM, + ); + } + return volunteerGroup; +}; + +/** + * This function checks if the volunteerMembership exists. + * @param membershipId - id + * @returns VolunteerMembership + */ +export const checkVolunteerMembershipExists = async ( + membershipId: string, +): Promise => { + const volunteerMembership = await VolunteerMembership.findOne({ + _id: membershipId, + }); + + if (!volunteerMembership) { + throw new errors.NotFoundError( + requestContext.translate( + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR.MESSAGE, + ), + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR.CODE, + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR.PARAM, + ); + } + return volunteerMembership; +}; diff --git a/src/utilities/encodedImageStorage/deletePreviousFile.ts b/src/utilities/encodedImageStorage/deletePreviousFile.ts new file mode 100644 index 00000000000..defeff0e577 --- /dev/null +++ b/src/utilities/encodedImageStorage/deletePreviousFile.ts @@ -0,0 +1,38 @@ +import { File } from "../../models"; +import { deleteFile } from "../../REST/services/minio"; +import { BUCKET_NAME } from "../../config/minio"; + +/** + * Deletes a file from the storage and database if its reference count is 1. + * Otherwise, decrements the reference count in the database by 1. + * + * @param fileId - The ID of the file to be deleted or updated. + * @param objectKey - The object key in the storage bucket associated with the file. + * @returns A promise that resolves when the file is either deleted or its reference count is updated. + */ +export const deletePreviousFile = async ( + fileId: string, + objectKey: string, +): Promise => { + const file = await File.findOne({ + _id: fileId, + }); + + if (file?.referenceCount === 1) { + await deleteFile(BUCKET_NAME as string, objectKey); + await File.deleteOne({ + _id: fileId, + }); + } else { + await File.findOneAndUpdate( + { + _id: fileId, + }, + { + $inc: { + referenceCount: -1, + }, + }, + ); + } +}; diff --git a/src/utilities/isValidMimeType.ts b/src/utilities/isValidMimeType.ts new file mode 100644 index 00000000000..1a121b93f8a --- /dev/null +++ b/src/utilities/isValidMimeType.ts @@ -0,0 +1,17 @@ +import type { FileMimeType } from "../REST/types"; + +/** + * Checks if the provided mimetype is valid. + * @param mimetype - The mimetype to check. + * @returns True if the mimetype is valid, false otherwise. + */ +export const isValidMimeType = (mimetype: string): mimetype is FileMimeType => { + const allowedMimeTypes: FileMimeType[] = [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "video/mp4", + ]; + return allowedMimeTypes.includes(mimetype as FileMimeType); +}; diff --git a/src/utilities/loadSampleData.ts b/src/utilities/loadSampleData.ts index 7ff7e666dae..d257cde79ac 100644 --- a/src/utilities/loadSampleData.ts +++ b/src/utilities/loadSampleData.ts @@ -11,6 +11,7 @@ import { Organization, Post, User, + Venue, } from "../models"; import { RecurrenceRule } from "../models/RecurrenceRule"; @@ -128,6 +129,9 @@ async function insertCollections(collections: string[]): Promise { case "events": await Event.insertMany(docs); break; + case "venue": + await Venue.insertMany(docs); + break; case "recurrenceRules": await RecurrenceRule.insertMany(docs); break; @@ -172,6 +176,7 @@ async function checkCountAfterImport(): Promise { { name: "events", model: Event }, { name: "recurrenceRules", model: RecurrenceRule }, { name: "posts", model: Post }, + { name: "venue", model: Venue }, { name: "appUserProfiles", model: AppUserProfile }, ]; @@ -201,6 +206,7 @@ const collections = [ "organizations", "posts", "events", + "venue", "recurrenceRules", "appUserProfiles", "actionItemCategories", diff --git a/src/utilities/removeSampleOrganizationUtil.ts b/src/utilities/removeSampleOrganizationUtil.ts index 089e97368a8..032717557c0 100644 --- a/src/utilities/removeSampleOrganizationUtil.ts +++ b/src/utilities/removeSampleOrganizationUtil.ts @@ -4,6 +4,7 @@ import { Organization, Plugin, Post, + Venue, SampleData, User, } from "../models"; @@ -27,6 +28,7 @@ export async function removeSampleOrganization(): Promise { Post, Event, User, + Venue, Plugin, AppUserProfile, }; diff --git a/src/utilities/userTagsPaginationUtils/getUserTagGraphQLConnectionFilter.ts b/src/utilities/userTagsPaginationUtils/getUserTagGraphQLConnectionFilter.ts new file mode 100644 index 00000000000..e7f6d88a2a6 --- /dev/null +++ b/src/utilities/userTagsPaginationUtils/getUserTagGraphQLConnectionFilter.ts @@ -0,0 +1,67 @@ +import type { GraphQLConnectionTraversalDirection } from "../graphQLConnection"; +import type { + ParseSortedByResult, + ParseUserTagWhereResult, +} from "../userTagsPaginationUtils"; + +/** + * This is typescript type of the object returned from function `getUserTagGraphQLConnectionFilter`. + */ +type BaseUserTagGraphQLConnectionFilter = { + name: { + $regex: RegExp; + }; +}; + +type UserTagGraphQLConnectionFilter = BaseUserTagGraphQLConnectionFilter & + ( + | { + _id?: { + $lt: string; + }; + } + | { + _id?: { + $gt: string; + }; + } + ); +/** + * This function is used to get an object containing filtering logic. + */ +export function getUserTagGraphQLConnectionFilter({ + cursor, + direction, + sortById, + nameStartsWith, +}: ParseSortedByResult & + ParseUserTagWhereResult & { + cursor: string | null; + direction: GraphQLConnectionTraversalDirection; + }): UserTagGraphQLConnectionFilter { + const filter = {} as UserTagGraphQLConnectionFilter; + + filter.name = { + $regex: new RegExp( + `^${nameStartsWith.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, + "i", + ), + }; + + if (cursor !== null) { + filter._id = getCursorFilter(cursor, sortById, direction); + } + + return filter; +} + +function getCursorFilter( + cursor: string, + sortById: "ASCENDING" | "DESCENDING", + direction: GraphQLConnectionTraversalDirection, +): { $lt: string } | { $gt: string } { + if (sortById === "ASCENDING") { + return direction === "BACKWARD" ? { $lt: cursor } : { $gt: cursor }; + } + return direction === "BACKWARD" ? { $gt: cursor } : { $lt: cursor }; +} diff --git a/src/utilities/userTagsPaginationUtils/getUserTagGraphQLConnectionSort.ts b/src/utilities/userTagsPaginationUtils/getUserTagGraphQLConnectionSort.ts new file mode 100644 index 00000000000..a5082f3d885 --- /dev/null +++ b/src/utilities/userTagsPaginationUtils/getUserTagGraphQLConnectionSort.ts @@ -0,0 +1,45 @@ +import type { GraphQLConnectionTraversalDirection } from "../graphQLConnection"; +import type { ParseSortedByResult } from "../userTagsPaginationUtils"; + +/** + *This is typescript type of the object returned from `getUserTagGraphQLConnectionSort` function. + */ +type UserTagGraphQLConnectionSort = + | { + _id: 1; + } + | { + _id: -1; + }; + +/** + * This function is used to get an object containing sorting logic.a + */ +export function getUserTagGraphQLConnectionSort({ + direction, + sortById, +}: ParseSortedByResult & { + direction: GraphQLConnectionTraversalDirection; +}): UserTagGraphQLConnectionSort { + if (sortById === "ASCENDING") { + if (direction === "BACKWARD") { + return { + _id: -1, + }; + } else { + return { + _id: 1, + }; + } + } else { + if (direction === "BACKWARD") { + return { + _id: 1, + }; + } else { + return { + _id: -1, + }; + } + } +} diff --git a/src/utilities/userTagsPaginationUtils/getUserTagMemberGraphQLConnectionFilter.ts b/src/utilities/userTagsPaginationUtils/getUserTagMemberGraphQLConnectionFilter.ts new file mode 100644 index 00000000000..3d00232d4e0 --- /dev/null +++ b/src/utilities/userTagsPaginationUtils/getUserTagMemberGraphQLConnectionFilter.ts @@ -0,0 +1,81 @@ +import { Types } from "mongoose"; +import type { GraphQLConnectionTraversalDirection } from "../graphQLConnection"; +import type { + ParseSortedByResult, + ParseUserTagMemberWhereResult, +} from "../userTagsPaginationUtils"; + +/** + * This is typescript type of the object returned from function `getUserTagMemberGraphQLConnectionFilter`. + */ +type BaseUserTagMemberGraphQLConnectionFilter = { + firstName: { + $regex: RegExp; + }; + lastName: { + $regex: RegExp; + }; +}; + +type UserTagMemberGraphQLConnectionFilter = + BaseUserTagMemberGraphQLConnectionFilter & + ( + | { + _id?: { + $lt: Types.ObjectId; + }; + } + | { + _id?: { + $gt: Types.ObjectId; + }; + } + ); + +/** + * This function is used to get an object containing filtering logic. + */ +export function getUserTagMemberGraphQLConnectionFilter({ + cursor, + direction, + sortById, + firstNameStartsWith, + lastNameStartsWith, +}: ParseSortedByResult & + ParseUserTagMemberWhereResult & { + cursor: string | null; + direction: GraphQLConnectionTraversalDirection; + }): UserTagMemberGraphQLConnectionFilter { + const filter = {} as UserTagMemberGraphQLConnectionFilter; + + filter.firstName = { + $regex: new RegExp( + `^${firstNameStartsWith.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, + "i", + ), + }; + filter.lastName = { + $regex: new RegExp( + `^${lastNameStartsWith.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, + "i", + ), + }; + + if (cursor !== null) { + filter._id = getCursorFilter(cursor, sortById, direction); + } + + return filter; +} + +function getCursorFilter( + cursor: string, + sortById: "ASCENDING" | "DESCENDING", + direction: GraphQLConnectionTraversalDirection, +): { $lt: Types.ObjectId } | { $gt: Types.ObjectId } { + const cursorId = new Types.ObjectId(cursor); + if (sortById === "ASCENDING") { + return direction === "BACKWARD" ? { $lt: cursorId } : { $gt: cursorId }; + } + return direction === "BACKWARD" ? { $gt: cursorId } : { $lt: cursorId }; +} diff --git a/src/utilities/userTagsPaginationUtils/index.ts b/src/utilities/userTagsPaginationUtils/index.ts new file mode 100644 index 00000000000..40b6dde26a1 --- /dev/null +++ b/src/utilities/userTagsPaginationUtils/index.ts @@ -0,0 +1,6 @@ +export * from "./parseUserTagSortedBy"; +export * from "./parseUserTagWhere"; +export * from "./parseUserTagMemberWhere"; +export * from "./getUserTagGraphQLConnectionSort"; +export * from "./getUserTagGraphQLConnectionFilter"; +export * from "./getUserTagMemberGraphQLConnectionFilter"; diff --git a/src/utilities/userTagsPaginationUtils/parseUserTagMemberWhere.ts b/src/utilities/userTagsPaginationUtils/parseUserTagMemberWhere.ts new file mode 100644 index 00000000000..636cb03087c --- /dev/null +++ b/src/utilities/userTagsPaginationUtils/parseUserTagMemberWhere.ts @@ -0,0 +1,73 @@ +import type { UserTagUsersAssignedToWhereInput } from "../../types/generatedGraphQLTypes"; +import type { + DefaultGraphQLArgumentError, + ParseGraphQLConnectionWhereResult, +} from "../graphQLConnection"; + +/** + * Type of the where object returned if the parsing is successful + */ +export type ParseUserTagMemberWhereResult = { + firstNameStartsWith: string; + lastNameStartsWith: string; +}; + +/** + * Function to parse the args.where for UserTag member assignment queries + */ +export function parseUserTagMemberWhere( + where: UserTagUsersAssignedToWhereInput | null | undefined, +): ParseGraphQLConnectionWhereResult { + const errors: DefaultGraphQLArgumentError[] = []; + + if (!where) { + return { + isSuccessful: true, + parsedWhere: { + firstNameStartsWith: "", + lastNameStartsWith: "", + }, + }; + } + + if (!where.firstName && !where.lastName) { + errors.push({ + message: `At least one of firstName or lastName should be provided`, + path: ["where"], + }); + + return { + isSuccessful: false, + errors, + }; + } + + if (where.firstName && typeof where.firstName.starts_with !== "string") { + errors.push({ + message: "Invalid firstName provided. It must be a string.", + path: ["where", "firstName", "starts_with"], + }); + } + + if (where.lastName && typeof where.lastName.starts_with !== "string") { + errors.push({ + message: "Invalid lastName provided. It must be a string.", + path: ["where", "lastName", "starts_with"], + }); + } + + if (errors.length > 0) { + return { + isSuccessful: false, + errors, + }; + } + + return { + isSuccessful: true, + parsedWhere: { + firstNameStartsWith: where.firstName?.starts_with.trim() ?? "", + lastNameStartsWith: where.lastName?.starts_with.trim() ?? "", + }, + }; +} diff --git a/src/utilities/userTagsPaginationUtils/parseUserTagSortedBy.ts b/src/utilities/userTagsPaginationUtils/parseUserTagSortedBy.ts new file mode 100644 index 00000000000..825fca40b6d --- /dev/null +++ b/src/utilities/userTagsPaginationUtils/parseUserTagSortedBy.ts @@ -0,0 +1,50 @@ +import type { + SortedByOrder, + UserTagSortedByInput, +} from "../../types/generatedGraphQLTypes"; +import type { + DefaultGraphQLArgumentError, + ParseGraphQLConnectionSortedByResult, +} from "../graphQLConnection"; + +/** + * type of the sort object returned if the parsing is successful + */ +export type ParseSortedByResult = { + sortById: SortedByOrder; +}; + +/** + * function to parse the args.sortedBy for UserTag queries + */ +export function parseUserTagSortedBy( + sortedBy: UserTagSortedByInput | null | undefined, +): ParseGraphQLConnectionSortedByResult { + const errors: DefaultGraphQLArgumentError[] = []; + + if (!sortedBy) { + return { + isSuccessful: true, + parsedSortedBy: { sortById: "DESCENDING" }, + }; + } + + if (sortedBy.id !== "DESCENDING" && sortedBy.id !== "ASCENDING") { + errors.push({ + message: + "Invalid sortedById provided. It must be a of type SortedByOrder.", + path: ["sortedBy", "id"], + }); + return { + isSuccessful: false, + errors, + }; + } + + return { + isSuccessful: true, + parsedSortedBy: { + sortById: sortedBy.id, + }, + }; +} diff --git a/src/utilities/userTagsPaginationUtils/parseUserTagWhere.ts b/src/utilities/userTagsPaginationUtils/parseUserTagWhere.ts new file mode 100644 index 00000000000..2f0728363df --- /dev/null +++ b/src/utilities/userTagsPaginationUtils/parseUserTagWhere.ts @@ -0,0 +1,61 @@ +import type { UserTagWhereInput } from "../../types/generatedGraphQLTypes"; +import type { + DefaultGraphQLArgumentError, + ParseGraphQLConnectionWhereResult, +} from "../graphQLConnection"; + +/** + * type of the where object returned if the parsing is successful + */ +export type ParseUserTagWhereResult = { + nameStartsWith: string; +}; + +/** + * function to parse the args.where for UserTag queries + */ +export function parseUserTagWhere( + where: UserTagWhereInput | null | undefined, +): ParseGraphQLConnectionWhereResult { + const errors: DefaultGraphQLArgumentError[] = []; + + if (!where) { + return { + isSuccessful: true, + parsedWhere: { + nameStartsWith: "", + }, + }; + } + + if (!where.name) { + errors.push({ + message: "Invalid where input, name should be provided.", + path: ["where"], + }); + + return { + isSuccessful: false, + errors, + }; + } + + if (typeof where.name.starts_with !== "string") { + errors.push({ + message: "Invalid name provided. It must be a string.", + path: ["where", "name"], + }); + + return { + isSuccessful: false, + errors, + }; + } + + return { + isSuccessful: true, + parsedWhere: { + nameStartsWith: where.name.starts_with.trim(), + }, + }; +} diff --git a/test1.txt b/test1.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/helpers/actionItem.ts b/tests/helpers/actionItem.ts index 86eab576296..b1897f717ec 100644 --- a/tests/helpers/actionItem.ts +++ b/tests/helpers/actionItem.ts @@ -35,6 +35,7 @@ export const createTestActionItem = async (): Promise< const testActionItem = await ActionItem.create({ creator: testUser?._id, assignee: randomUser?._id, + assigneeType: "EventVolunteer", assigner: testUser?._id, actionItemCategory: testCategory?._id, organization: testOrganization?._id, @@ -59,6 +60,7 @@ export const createNewTestActionItem = async ({ const newTestActionItem = await ActionItem.create({ creator: currUserId, assignee: assignedUserId, + assigneeType: "EventVolunteer", assigner: currUserId, actionItemCategory: actionItemCategoryId, organization: organizationId, @@ -82,6 +84,7 @@ export const createTestActionItems = async (): Promise< const testActionItem1 = await ActionItem.create({ creator: testUser?._id, assignee: randomUser?._id, + assigneeType: "EventVolunteer", assigner: testUser?._id, actionItemCategory: testCategory?._id, organization: testOrganization?._id, @@ -91,6 +94,7 @@ export const createTestActionItems = async (): Promise< const testActionItem2 = await ActionItem.create({ creator: testUser?._id, assignee: randomUser?._id, + assigneeType: "EventVolunteer", assigner: testUser?._id, actionItemCategory: testCategory?._id, organization: testOrganization?._id, @@ -100,6 +104,7 @@ export const createTestActionItems = async (): Promise< await ActionItem.create({ creator: testUser?._id, assignee: randomUser?._id, + assigneeType: "EventVolunteer", assigner: testUser?._id, actionItemCategory: testCategory2?._id, organization: testOrganization?._id, diff --git a/tests/helpers/chat.ts b/tests/helpers/chat.ts new file mode 100644 index 00000000000..b3fa7b468a0 --- /dev/null +++ b/tests/helpers/chat.ts @@ -0,0 +1,139 @@ +import { nanoid } from "nanoid"; +import type { InterfaceChat, InterfaceChatMessage } from "../../src/models"; +import { Chat, ChatMessage } from "../../src/models"; +import type { TestOrganizationType, TestUserType } from "./userAndOrg"; +import { createTestUserAndOrganization } from "./userAndOrg"; +import type { Document } from "mongoose"; + +export type TestChatType = + | (InterfaceChat & Document) + | null; + +export type TestChatMessageType = + | (InterfaceChatMessage & Document) + | null; + +export const createTestChat = async (): Promise< + [TestUserType, TestOrganizationType, TestChatType] +> => { + const [testUser, testOrganization] = await createTestUserAndOrganization(); + if (testUser && testOrganization) { + const testChat = await Chat.create({ + creatorId: testUser._id, + users: [testUser._id], + organization: testOrganization._id, + isGroup: true, + createdAt: new Date(), + updatedAt: new Date(), + admins: [testUser._id], + }); + + return [testUser, testOrganization, testChat]; + } else { + return [testUser, testOrganization, null]; + } +}; + +export const createTestChatMessage = async (): Promise< + [TestUserType, TestOrganizationType, TestChatType, TestChatMessageType] +> => { + const [testUser, testOrganization, testChat] = await createTestChat(); + + if (testChat?.id) { + const chatMessage = await createChatMessage( + testUser?._id, + testChat?._id.toString(), + ); + + if (testChat && testUser) { + const testChatMessage = await ChatMessage.create({ + chatMessageBelongsTo: testChat._id, + sender: testUser._id, + replyTo: chatMessage?._id, + messageContent: `msgContent${nanoid().toLowerCase()}`, + createdAt: new Date(), + updatedAt: new Date(), + type: "STRING", + }); + return [testUser, testOrganization, testChat, testChatMessage]; + } else { + return [testUser, testOrganization, testChat, null]; + } + } + return [testUser, testOrganization, testChat, null]; +}; + +export const createTestChatMessageWithoutReply = async (): Promise< + [TestUserType, TestOrganizationType, TestChatType, TestChatMessageType] +> => { + const [testUser, testOrganization, testChat] = await createTestChat(); + + if (testChat && testUser) { + const testChatMessage = await ChatMessage.create({ + chatMessageBelongsTo: testChat._id, + sender: testUser._id, + replyTo: undefined, + messageContent: `msgContent${nanoid().toLowerCase()}`, + createdAt: new Date(), + updatedAt: new Date(), + type: "STRING", + }); + return [testUser, testOrganization, testChat, testChatMessage]; + } else { + return [testUser, testOrganization, testChat, null]; + } +}; + +export const createTestMessageForMultipleUser = async ( + senderId: string, + organizationId: string, +): Promise => { + const testChat = await Chat.create({ + creatorId: senderId, + users: [senderId], + organization: organizationId, + }); + + await ChatMessage.create({ + chatMessageBelongsTo: testChat._id, + sender: senderId, + messageContent: `messageContent${nanoid().toLowerCase()}`, + createdAt: new Date(), + updatedAt: new Date(), + type: "STRING", + }); + + return testChat; +}; + +export const createTestChatwithUsers = async ( + creator: string, + organizationId: string, + users: string[], +): Promise => { + const testChat = await Chat.create({ + creatorId: creator, + users: users, + organization: organizationId, + isGroup: true, + createdAt: new Date(), + updatedAt: new Date(), + }); + return testChat; +}; + +export const createChatMessage = async ( + senderId: string, + chatId: string, +): Promise => { + const chatMessage = await ChatMessage.create({ + chatMessageBelongsTo: chatId, + sender: senderId, + type: "STRING", + messageContent: `messageContent${nanoid().toLowerCase()}`, + createdAt: new Date(), + updatedAt: new Date(), + }); + + return chatMessage; +}; diff --git a/tests/helpers/directChat.ts b/tests/helpers/directChat.ts deleted file mode 100644 index 0b4d523b239..00000000000 --- a/tests/helpers/directChat.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { nanoid } from "nanoid"; -import type { - InterfaceDirectChat, - InterfaceDirectChatMessage, -} from "../../src/models"; -import { DirectChat, DirectChatMessage } from "../../src/models"; -import type { TestOrganizationType, TestUserType } from "./userAndOrg"; -import { createTestUserAndOrganization } from "./userAndOrg"; -import type { Document } from "mongoose"; - -export type TestDirectChatType = - | (InterfaceDirectChat & Document) - | null; - -export type TestDirectChatMessageType = - | (InterfaceDirectChatMessage & - Document) - | null; - -export const createTestDirectChat = async (): Promise< - [TestUserType, TestOrganizationType, TestDirectChatType] -> => { - const [testUser, testOrganization] = await createTestUserAndOrganization(); - if (testUser && testOrganization) { - const testDirectChat = await DirectChat.create({ - creatorId: testUser._id, - users: [testUser._id], - organization: testOrganization._id, - }); - - return [testUser, testOrganization, testDirectChat]; - } else { - return [testUser, testOrganization, null]; - } -}; - -export const createTestDirectChatMessage = async (): Promise< - [ - TestUserType, - TestOrganizationType, - TestDirectChatType, - TestDirectChatMessageType, - ] -> => { - const [testUser, testOrganization, testDirectChat] = - await createTestDirectChat(); - - if (testDirectChat && testUser) { - const testDirectChatMessage = await DirectChatMessage.create({ - directChatMessageBelongsTo: testDirectChat._id, - sender: testUser._id, - receiver: testUser._id, - messageContent: `msgContent${nanoid().toLowerCase()}`, - }); - return [testUser, testOrganization, testDirectChat, testDirectChatMessage]; - } else { - return [testUser, testOrganization, testDirectChat, null]; - } -}; - -export const createTestDirectMessageForMultipleUser = async ( - senderId: string, - receiverId: string, - organizationId: string, -): Promise => { - const testDirectChat = await DirectChat.create({ - creatorId: senderId, - users: [senderId], - organization: organizationId, - }); - - await DirectChatMessage.create({ - directChatMessageBelongsTo: testDirectChat._id, - sender: senderId, - receiver: receiverId, - messageContent: `messageContent${nanoid().toLowerCase()}`, - }); - - return testDirectChat; -}; - -export const createTestDirectChatwithUsers = async ( - creator: string, - organizationId: string, - users: string[], -): Promise => { - const testDirectChat = await DirectChat.create({ - creatorId: creator, - users: users, - organization: organizationId, - }); - return testDirectChat; -}; - -export const createDirectChatMessage = async ( - senderId: string, - receiverId: string, - directChatId: string, -): Promise => { - const directChatMessage = await DirectChatMessage.create({ - directChatMessageBelongsTo: directChatId, - sender: senderId, - receiver: receiverId, - messageContent: `messageContent${nanoid().toLowerCase()}`, - }); - - return directChatMessage; -}; diff --git a/tests/helpers/events.ts b/tests/helpers/events.ts index 75298ee74a5..13130906a87 100644 --- a/tests/helpers/events.ts +++ b/tests/helpers/events.ts @@ -1,6 +1,5 @@ import type { Document } from "mongoose"; import { nanoid } from "nanoid"; -import { EventVolunteerResponse } from "../../src/constants"; import type { InterfaceEvent, InterfaceEventVolunteer, @@ -132,12 +131,11 @@ export const createTestEventAndVolunteer = async (): Promise< const [creatorUser, , testEvent] = await createTestEvent(); const volunteerUser = await createTestUser(); const testEventVolunteer = await EventVolunteer.create({ - userId: volunteerUser?._id, - eventId: testEvent?._id, - isInvited: true, - isAssigned: false, - creatorId: creatorUser?._id, - response: EventVolunteerResponse.NO, + user: volunteerUser?._id, + event: testEvent?._id, + creator: creatorUser?._id, + hasAccepted: false, + isPublic: false, }); return [volunteerUser, creatorUser, testEvent, testEventVolunteer]; @@ -157,9 +155,9 @@ export const createTestEventVolunteerGroup = async (): Promise< const testEventVolunteerGroup = await EventVolunteerGroup.create({ name: "testEventVolunteerGroup", volunteersRequired: 1, - eventId: testEvent?._id, - creatorId: creatorUser?._id, - leaderId: creatorUser?._id, + event: testEvent?._id, + creator: creatorUser?._id, + leader: creatorUser?._id, volunteers: [testEventVolunteer?._id], }); diff --git a/tests/helpers/groupChat.ts b/tests/helpers/groupChat.ts deleted file mode 100644 index a41591c962b..00000000000 --- a/tests/helpers/groupChat.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { nanoid } from "nanoid"; -import type { - InterfaceGroupChat, - InterfaceGroupChatMessage, -} from "../../src/models"; -import { GroupChat, GroupChatMessage } from "../../src/models"; -import type { TestOrganizationType, TestUserType } from "./userAndOrg"; -import { createTestUserAndOrganization } from "./userAndOrg"; -import type { Document } from "mongoose"; - -export type TestGroupChatType = - | (InterfaceGroupChat & Document) - | null; - -export type TestGroupChatMessageType = - | (InterfaceGroupChatMessage & - Document) - | null; - -export const createTestGroupChat = async (): Promise< - [TestUserType, TestOrganizationType, TestGroupChatType] -> => { - const [testUser, testOrganization] = await createTestUserAndOrganization(); - if (testUser && testOrganization) { - const testGroupChat = await GroupChat.create({ - creatorId: testUser._id, - users: [testUser._id], - organization: testOrganization._id, - title: `title${nanoid().toLowerCase()}`, - }); - - return [testUser, testOrganization, testGroupChat]; - } else { - return [testUser, testOrganization, null]; - } -}; - -export const createTestGroupChatMessage = async (): Promise< - [ - TestUserType, - TestOrganizationType, - TestGroupChatType, - TestGroupChatMessageType, - ] -> => { - const [testUser, testOrganization, testGroupChat] = - await createTestGroupChat(); - - if (testGroupChat && testUser) { - const testGroupChatMessage = await GroupChatMessage.create({ - groupChatMessageBelongsTo: testGroupChat._id, - sender: testUser._id, - createdAt: new Date(), - messageContent: `messageContent${nanoid().toLowerCase()}`, - }); - - return [testUser, testOrganization, testGroupChat, testGroupChatMessage]; - } else { - return [testUser, testOrganization, testGroupChat, null]; - } -}; diff --git a/tests/helpers/minio.ts b/tests/helpers/minio.ts new file mode 100644 index 00000000000..e19f51b8090 --- /dev/null +++ b/tests/helpers/minio.ts @@ -0,0 +1,49 @@ +import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3"; +import { ConnectionTimeoutError } from "redis"; + +/** + * Creates an S3 client configured for testing with MinIO + */ +const createTestS3Client = (): S3Client => { + return new S3Client({ + endpoint: process.env.MINIO_ENDPOINT || "http://localhost:9000", + credentials: { + accessKeyId: process.env.MINIO_ROOT_USER || "minioadmin", + secretAccessKey: process.env.MINIO_ROOT_PASSWORD || "minioadmin", + }, + region: "us-east-1", + forcePathStyle: true, + requestHandler: { + connectionTimeout: 3000, + socketTimeout: 3000, + }, + }); +}; + +/** + * Checks if the MinIO server is running and accessible + */ +export const isMinioRunning = async (): Promise => { + const s3Client = createTestS3Client(); + try { + await s3Client.send(new ListBucketsCommand({})); + return true; + } catch (error: unknown) { + if ( + (error instanceof ConnectionTimeoutError && + error.name === "ConnectTimeoutError") || + (error instanceof Error && error.name === "NetworkError") || + (error instanceof Error && + (error as { code?: string }).code === "ECONNREFUSED") + ) { + console.warn( + "\x1b[33m%s\x1b[0m", + "⚠️ MinIO server is not running. Some tests will be skipped.\n" + + "To run all tests, start the MinIO server first.", + ); + return false; + } + // If it's a different kind of error (e.g., authentication), we still consider the server as running + return true; + } +}; diff --git a/tests/helpers/posts.ts b/tests/helpers/posts.ts index c17a7c27528..38e97ca0955 100644 --- a/tests/helpers/posts.ts +++ b/tests/helpers/posts.ts @@ -1,7 +1,7 @@ import type { Document } from "mongoose"; import { nanoid } from "nanoid"; import type { InterfaceComment, InterfacePost } from "../../src/models"; -import { Comment, Organization, Post } from "../../src/models"; +import { Comment, Organization, Post, File } from "../../src/models"; import type { TestOrganizationType, TestUserType } from "./userAndOrg"; import { createTestUserAndOrganization } from "./userAndOrg"; @@ -119,19 +119,33 @@ export const createSinglePostwithComment = async ( return [testPost, testComment]; }; -export const createTestSinglePost = async ( +export const createTestPostWithMedia = async ( userId: string, organizationId: string, pinned = false, ): Promise => { + const testFile = await File.create({ + fileName: `test-file-${nanoid()}.jpg`, + mimeType: "image/jpeg", + size: 1024, + hash: { + value: "66465102d50336a0610af4ae66d531cc", + algorithm: "sha256", + }, + uri: "https://example.com/test-file.jpg", + metadata: { + description: "Test file for post", + objectKey: "test-file-object-key", + }, + }); + const testPost = await Post.create({ text: `text${nanoid().toLowerCase()}`, title: `title${nanoid()}`, - imageUrl: `imageUrl${nanoid()}`, - videoUrl: `videoUrl${nanoid()}`, creatorId: userId, organization: organizationId, pinned, + file: testFile, }); return testPost; }; diff --git a/tests/helpers/tags.ts b/tests/helpers/tags.ts index ddbf70cc8a7..06d3f49e576 100644 --- a/tests/helpers/tags.ts +++ b/tests/helpers/tags.ts @@ -79,6 +79,7 @@ export const createAndAssignUsersToTag = async ( await TagUser.create({ userId: user?._id, tagId: tag?._id, + organizationId: tag?.organizationId, }); testUsers.push(user); } @@ -93,6 +94,7 @@ export const createTagsAndAssignToUser = async ( await TagUser.create({ userId: testUser?._id, tagId: testTag?._id, + organizationId: testTag?.organizationId, }); const tags: TestUserTagType[] = [testTag]; @@ -108,6 +110,7 @@ export const createTagsAndAssignToUser = async ( await TagUser.create({ tagId: newTag?._id, userId: testUser?._id, + organizationId: newTag.organizationId, }); } diff --git a/tests/helpers/userAndOrg.ts b/tests/helpers/userAndOrg.ts index d7dde673dde..015b86a657b 100644 --- a/tests/helpers/userAndOrg.ts +++ b/tests/helpers/userAndOrg.ts @@ -9,8 +9,8 @@ import type { import { AppUserProfile, Organization, User } from "../../src/models"; export type TestOrganizationType = - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (InterfaceOrganization & Document) | null; + | (InterfaceOrganization & Document) + | null; export type TestUserType = | (InterfaceUser & Document) diff --git a/tests/helpers/volunteers.ts b/tests/helpers/volunteers.ts new file mode 100644 index 00000000000..026dc4313ab --- /dev/null +++ b/tests/helpers/volunteers.ts @@ -0,0 +1,299 @@ +import type { + InterfaceEventVolunteer, + InterfaceEventVolunteerGroup, + InterfaceVolunteerMembership, +} from "../../src/models"; +import { + ActionItem, + ActionItemCategory, + Event, + EventVolunteer, + EventVolunteerGroup, +} from "../../src/models"; +import type { Document } from "mongoose"; +import { + createTestUser, + createTestUserAndOrganization, + type TestOrganizationType, + type TestUserType, +} from "./userAndOrg"; +import { nanoid } from "nanoid"; +import type { TestEventType } from "./events"; +import type { TestActionItemType } from "./actionItem"; + +export type TestVolunteerType = InterfaceEventVolunteer & Document; +export type TestVolunteerGroupType = InterfaceEventVolunteerGroup & Document; +export type TestVolunteerMembership = InterfaceVolunteerMembership & Document; + +export const createTestVolunteerAndGroup = async (): Promise< + [ + TestUserType, + TestOrganizationType, + TestEventType, + TestVolunteerType, + TestVolunteerGroupType, + ] +> => { + const [testUser, testOrganization] = await createTestUserAndOrganization(); + const randomUser = await createTestUser(); + + const testEvent = await Event.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + allDay: true, + startDate: new Date(), + recurring: false, + isPublic: true, + isRegisterable: true, + creatorId: testUser?._id, + admins: [testUser?._id], + organization: testOrganization?._id, + volunteers: [], + volunteerGroups: [], + }); + + const testVolunteer = await EventVolunteer.create({ + creator: randomUser?._id, + event: testEvent?._id, + user: testUser?._id, + groups: [], + assignments: [], + }); + + // create a volunteer group with testVolunteer as a member & leader + const testVolunteerGroup = await EventVolunteerGroup.create({ + creator: randomUser?._id, + event: testEvent?._id, + volunteers: [testVolunteer?._id], + leader: testVolunteer?._id, + assignments: [], + name: "Test Volunteer Group 1", + }); + + // add volunteer & group to event + await Event.updateOne( + { + _id: testEvent?._id, + }, + { + $push: { + volunteers: testVolunteer?._id, + volunteerGroups: testVolunteerGroup?._id, + }, + }, + ); + + // add group to volunteer + await EventVolunteer.updateOne( + { + _id: testVolunteer?._id, + }, + { + $push: { + groups: testVolunteerGroup?._id, + }, + }, + ); + + return [ + testUser, + testOrganization, + testEvent, + testVolunteer, + testVolunteerGroup, + ]; +}; + +export const createVolunteerAndActions = async (): Promise< + [ + TestOrganizationType, + TestEventType, + TestUserType, + TestUserType, + TestVolunteerType, + TestVolunteerType, + TestVolunteerGroupType, + TestActionItemType, + TestActionItemType, + ] +> => { + const [testUser, testOrganization] = await createTestUserAndOrganization(); + const testUser2 = await createTestUser(); + + const randomUser = await createTestUser(); + + const testEvent = await Event.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + allDay: true, + startDate: new Date(), + recurring: false, + isPublic: true, + isRegisterable: true, + creatorId: testUser?._id, + admins: [testUser?._id], + organization: testOrganization?._id, + volunteers: [], + volunteerGroups: [], + }); + + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + const twoWeeksAgo = new Date(today); + twoWeeksAgo.setDate(today.getDate() - 14); + const twoMonthsAgo = new Date(today); + twoMonthsAgo.setMonth(today.getMonth() - 2); + const twoYearsAgo = new Date(today); + twoYearsAgo.setFullYear(today.getFullYear() - 2); + + const testVolunteer1 = await EventVolunteer.create({ + creator: randomUser?._id, + event: testEvent?._id, + user: testUser?._id, + groups: [], + assignments: [], + hasAccepted: true, + hoursVolunteered: 10, + hoursHistory: [ + { + hours: 2, + date: yesterday, + }, + { + hours: 4, + date: twoWeeksAgo, + }, + { + hours: 2, + date: twoMonthsAgo, + }, + { + hours: 2, + date: twoYearsAgo, + }, + ], + }); + + const testVolunteer2 = await EventVolunteer.create({ + creator: randomUser?._id, + event: testEvent?._id, + user: testUser2?._id, + groups: [], + assignments: [], + hasAccepted: true, + hoursVolunteered: 8, + hoursHistory: [ + { + hours: 1, + date: yesterday, + }, + { + hours: 2, + date: twoWeeksAgo, + }, + { + hours: 3, + date: twoMonthsAgo, + }, + { + hours: 2, + date: twoYearsAgo, + }, + ], + }); + + // create a volunteer group with testVolunteer1 as a member & leader + const testVolunteerGroup = await EventVolunteerGroup.create({ + creator: randomUser?._id, + event: testEvent?._id, + volunteers: [testVolunteer1?._id, testVolunteer2?._id], + leader: testUser?._id, + assignments: [], + name: "Test Volunteer Group 1", + }); + + // add volunteer & group to event + await Event.updateOne( + { + _id: testEvent?._id, + }, + { + $addToSet: { + volunteers: { $each: [testVolunteer1?._id, testVolunteer2?._id] }, + volunteerGroups: testVolunteerGroup?._id, + }, + }, + ); + + const testActionItemCategory = await ActionItemCategory.create({ + creatorId: randomUser?._id, + organizationId: testOrganization?._id, + name: "Test Action Item Category 1", + isDisabled: false, + }); + + const testActionItem1 = await ActionItem.create({ + creator: randomUser?._id, + assigner: randomUser?._id, + assignee: testVolunteer1?._id, + assigneeType: "EventVolunteer", + assigneeGroup: null, + assigneeUser: null, + actionItemCategory: testActionItemCategory?._id, + event: testEvent?._id, + organization: testOrganization?._id, + allottedHours: 2, + assignmentDate: new Date(), + dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + isCompleted: false, + }); + + const testActionItem2 = await ActionItem.create({ + creator: randomUser?._id, + assigner: randomUser?._id, + assigneeType: "EventVolunteerGroup", + assigneeGroup: testVolunteerGroup?._id, + assignee: null, + assigneeUser: null, + actionItemCategory: testActionItemCategory?._id, + event: testEvent?._id, + organization: testOrganization?._id, + allottedHours: 4, + assignmentDate: new Date(), + dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 2000), + isCompleted: false, + }); + + await EventVolunteer.findByIdAndUpdate(testVolunteer1?._id, { + $push: { + assignments: testActionItem1?._id, + }, + }); + + await EventVolunteer.updateMany( + { _id: { $in: [testVolunteer1?._id, testVolunteer2?._id] } }, + { + $push: { + groups: testVolunteerGroup?._id, + assignments: testActionItem2?._id, + }, + }, + ); + + await EventVolunteerGroup.findByIdAndUpdate(testVolunteerGroup?._id, { + $addToSet: { assignments: testActionItem2 }, + }); + + return [ + testOrganization, + testEvent, + testUser, + testUser2, + testVolunteer1, + testVolunteer2, + testVolunteerGroup, + testActionItem1, + testActionItem2, + ]; +}; diff --git a/tests/middleware/fileUpload.spec.ts b/tests/middleware/fileUpload.spec.ts new file mode 100644 index 00000000000..5cdd0d7ed60 --- /dev/null +++ b/tests/middleware/fileUpload.spec.ts @@ -0,0 +1,215 @@ +import { describe, test, expect, vi, beforeEach } from "vitest"; +import type { Request, Response } from "express"; +import express from "express"; +import request from "supertest"; + +import { upload } from "../../src/config/multer"; +import { fileUpload } from "../../src/middleware"; +import { + CONTENT_TYPE_SHOULD_BE_MULTIPART_FORM_DATA, + FILE_SIZE_EXCEEDED, + IMAGE_SIZE_LIMIT, + INVALID_FILE_FIELD_NAME, + VIDEO_SIZE_LIMIT, +} from "../../src/constants"; + +vi.mock("../../src/libraries/requestContext", () => ({ + translate: (message: string): string => message, +})); + +describe("fileUpload Middleware", () => { + let app: express.Application; + + beforeEach(() => { + // Reset mocks + vi.clearAllMocks(); + + // Create new Express app for each test + app = express(); + + // Add json and urlencoded middleware + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); + + // Add test route with the middleware + app.post("/upload", fileUpload("file"), (_req: Request, res: Response) => { + res.status(200).json({ message: "Upload successful" }); + }); + }); + + test("should reject requests without multipart/form-data content type", async () => { + const response = await request(app) + .post("/upload") + .set("Content-Type", "application/json") + .send({ data: "test" }); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: CONTENT_TYPE_SHOULD_BE_MULTIPART_FORM_DATA.MESSAGE, + }); + }); + + test("should reject request with no file", async () => { + const imageBuffer = Buffer.from("fake image content"); + const response = await request(app) + .post("/upload") + .set("Content-Type", "multipart/form-data") + .field("someField", "someValue") + .attach("different-file-field-name", imageBuffer, { + filename: "test.jpg", + contentType: "image/jpeg", + }); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: INVALID_FILE_FIELD_NAME.MESSAGE, + }); + }); + + test("should accept valid image upload", async () => { + // Create a small buffer to simulate an image file + const imageBuffer = Buffer.from("fake image content"); + + const response = await request(app) + .post("/upload") + .attach("file", imageBuffer, { + filename: "test.jpg", + contentType: "image/jpeg", + }); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + message: "Upload successful", + }); + }); + + test("should accept valid video upload", async () => { + // Create a small buffer to simulate a video file + const videoBuffer = Buffer.from("fake video content"); + + const response = await request(app) + .post("/upload") + .attach("file", videoBuffer, { + filename: "test.mp4", + contentType: "video/mp4", + }); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + message: "Upload successful", + }); + }); + + test("should reject image exceeding size limit", async () => { + const largeImageBuffer = Buffer.alloc(IMAGE_SIZE_LIMIT + 1); + + const response = await request(app) + .post("/upload") + .attach("file", largeImageBuffer, { + filename: "large.jpg", + contentType: "image/jpeg", + }); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: FILE_SIZE_EXCEEDED.MESSAGE, + description: "Image size exceeds the limit of 5MB", + }); + }); + + test("should reject video exceeding size limit", async () => { + const largeVideoBuffer = Buffer.alloc(VIDEO_SIZE_LIMIT + 1); + + const response = await request(app) + .post("/upload") + .attach("file", largeVideoBuffer, { + filename: "large.mp4", + contentType: "video/mp4", + }); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: FILE_SIZE_EXCEEDED.MESSAGE, + description: "Video size exceeds the limit of 50MB", + }); + }); + + test("should throw error on exceeding the multer max file size", async () => { + const largeVideoBuffer = Buffer.alloc(VIDEO_SIZE_LIMIT + 3); + + const response = await request(app) + .post("/upload") + .attach("file", largeVideoBuffer, { + filename: "large.mp4", + contentType: "video/mp4", + }); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: "File too large", + }); + }); + + test("should handle multiple files correctly", async () => { + const imageBuffer = Buffer.from("fake image content"); + + const response = await request(app) + .post("/upload") + .attach("file", imageBuffer, "test1.jpg") + .attach("anotherFile", imageBuffer, "test2.jpg"); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: INVALID_FILE_FIELD_NAME.MESSAGE, + }); + }); + + test("should accept request with no file when content-type is correct", async () => { + const response = await request(app) + .post("/upload") + .set("Content-Type", "multipart/form-data") + .field("someField", "someValue"); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + message: "Upload successful", + }); + }); + + test("should reject files with wrong field name", async () => { + const imageBuffer = Buffer.from("fake image content"); + + const response = await request(app) + .post("/upload") + .attach("wrongField", imageBuffer, "test.jpg"); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: INVALID_FILE_FIELD_NAME.MESSAGE, + }); + }); + + test("should handle generic upload errors", async () => { + const multerMock = vi.spyOn(upload, "single").mockImplementation(() => { + return (req, res, next): void => { + next(new Error("Generic upload error")); + }; + }); + + const imageBuffer = Buffer.from("fake image content"); + + const response = await request(app) + .post("/upload") + .attach("file", imageBuffer, { + filename: "test.jpg", + contentType: "image/jpeg", + }); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + error: "File upload failed", + }); + + multerMock.mockRestore(); + }); +}); diff --git a/tests/middleware/isAuth.spec.ts b/tests/middleware/isAuth.spec.ts index 78f685ca435..90a73c88ca9 100644 --- a/tests/middleware/isAuth.spec.ts +++ b/tests/middleware/isAuth.spec.ts @@ -1,10 +1,18 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Request } from "express"; -import { isAuth } from "../../src/middleware/isAuth"; +import type { NextFunction, Request, Response } from "express"; +import type { InterfaceAuthenticatedRequest } from "../../src/middleware/isAuth"; +import { isAuth, isAuthMiddleware } from "../../src/middleware/isAuth"; import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; import jwt from "jsonwebtoken"; import { logger } from "../../src/libraries/logger"; -import { ACCESS_TOKEN_SECRET } from "../../src/constants"; +import { + ACCESS_TOKEN_SECRET, + UNAUTHENTICATED_ERROR, +} from "../../src/constants"; + +vi.mock("../../src/libraries/requestContext", () => ({ + translate: (message: string): string => message, +})); interface TestInterfaceAuthData { isAuth: boolean; @@ -197,3 +205,69 @@ describe("middleware -> isAuth", () => { expect(authData).toEqual(testAuthData); }); }); + +describe("isAuthMiddleware", () => { + let mockRequest: Partial; + let mockResponse: Partial; + let nextFunction: NextFunction; + + beforeEach(() => { + mockRequest = { + headers: {}, + }; + mockResponse = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + } as unknown as Response; + nextFunction = vi.fn(); + + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should call next() when user is authenticated", () => { + // Mock successful token verification + vi.spyOn(jwt, "verify").mockImplementationOnce((...args: any) => { + const decoded = { + userId: "ValidUserId", + }; + const callBackFn = args[2]; + return callBackFn(null, decoded); + }); + + mockRequest.headers = { + authorization: "Bearer validToken", + }; + + isAuthMiddleware( + mockRequest as Request, + mockResponse as Response, + nextFunction, + ); + + expect(nextFunction).toHaveBeenCalled(); + expect(mockRequest.isAuth).toBe(true); + expect(mockRequest.userId).toBe("ValidUserId"); + expect(mockRequest.tokenExpired).toBeUndefined(); + expect(mockResponse.status).not.toHaveBeenCalled(); + expect(mockResponse.json).not.toHaveBeenCalled(); + }); + + it("should return 401 when token is not present", () => { + isAuthMiddleware( + mockRequest as Request, + mockResponse as Response, + nextFunction, + ); + + expect(nextFunction).not.toHaveBeenCalled(); + expect(mockResponse.status).toHaveBeenCalledWith(401); + expect(mockResponse.json).toHaveBeenCalledWith({ + message: UNAUTHENTICATED_ERROR.MESSAGE, + expired: undefined, + }); + }); +}); diff --git a/tests/resolvers/Chat/admins.spec.ts b/tests/resolvers/Chat/admins.spec.ts new file mode 100644 index 00000000000..00b752b622a --- /dev/null +++ b/tests/resolvers/Chat/admins.spec.ts @@ -0,0 +1,42 @@ +import "dotenv/config"; +import { admins as adminsResolver } from "../../../src/resolvers/Chat/admins"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { User } from "../../../src/models"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import type { TestChatType } from "../../helpers/chat"; +import { createTestChatMessage } from "../../helpers/chat"; + +let testChat: TestChatType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [, , testChat] = await createTestChatMessage(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Chat -> admins", () => { + it(`resolves the correct admin users for the given chat`, async () => { + const parent = testChat?.toObject(); + if (!parent) { + throw new Error("Parent object is undefined."); + } + + if (!adminsResolver) { + throw new Error("adminsResolver is not defined."); + } + const usersPayload = await adminsResolver(parent, {}, {}); + + const users = await User.find({ + _id: { + $in: testChat?.admins, + }, + }).lean(); + + expect(usersPayload).toEqual(users); + }); +}); diff --git a/tests/resolvers/DirectChat/creator.spec.ts b/tests/resolvers/Chat/creator.spec.ts similarity index 65% rename from tests/resolvers/DirectChat/creator.spec.ts rename to tests/resolvers/Chat/creator.spec.ts index ace3d79c367..4a1554d00d1 100644 --- a/tests/resolvers/DirectChat/creator.spec.ts +++ b/tests/resolvers/Chat/creator.spec.ts @@ -1,28 +1,28 @@ import "dotenv/config"; -import { creator as creatorResolver } from "../../../src/resolvers/DirectChat/creator"; +import { creator as creatorResolver } from "../../../src/resolvers/Chat/creator"; import { connect, disconnect } from "../../helpers/db"; import type mongoose from "mongoose"; import { User } from "../../../src/models"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import type { TestDirectChatType } from "../../helpers/directChat"; -import { createTestDirectChat } from "../../helpers/directChat"; +import type { TestChatType } from "../../helpers/chat"; +import { createTestChat } from "../../helpers/chat"; -let testDirectChat: TestDirectChatType; +let testChat: TestChatType; let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const userOrgChat = await createTestDirectChat(); - testDirectChat = userOrgChat[2]; + const userOrgChat = await createTestChat(); + testChat = userOrgChat[2]; }); afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> DirectChat -> creator", () => { +describe("resolvers -> Chat -> creator", () => { it(`returns user object for parent.creator`, async () => { - const parent = testDirectChat?.toObject(); + const parent = testChat?.toObject(); if (!parent) { throw new Error("Parent object is undefined."); } @@ -30,7 +30,7 @@ describe("resolvers -> DirectChat -> creator", () => { const creatorPayload = await creatorResolver?.(parent, {}, {}); const creator = await User.findOne({ - _id: testDirectChat?.creatorId, + _id: testChat?.creatorId, }).lean(); expect(creatorPayload).toEqual(creator); diff --git a/tests/resolvers/DirectChat/messages.spec.ts b/tests/resolvers/Chat/messages.spec.ts similarity index 57% rename from tests/resolvers/DirectChat/messages.spec.ts rename to tests/resolvers/Chat/messages.spec.ts index fb1632b790f..05875ce534f 100644 --- a/tests/resolvers/DirectChat/messages.spec.ts +++ b/tests/resolvers/Chat/messages.spec.ts @@ -1,37 +1,37 @@ import "dotenv/config"; -import { messages as messagesResolver } from "../../../src/resolvers/DirectChat/messages"; +import { messages as messagesResolver } from "../../../src/resolvers/Chat/messages"; import { connect, disconnect } from "../../helpers/db"; import type mongoose from "mongoose"; -import { DirectChatMessage } from "../../../src/models"; +import { ChatMessage } from "../../../src/models"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import type { TestDirectChatType } from "../../helpers/directChat"; -import { createTestDirectChatMessage } from "../../helpers/directChat"; +import type { TestChatType } from "../../helpers/chat"; +import { createTestChatMessage } from "../../helpers/chat"; -let testDirectChat: TestDirectChatType; +let testChat: TestChatType; let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const userOrgChat = await createTestDirectChatMessage(); - testDirectChat = userOrgChat[2]; + const userOrgChat = await createTestChatMessage(); + testChat = userOrgChat[2]; }); afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> DirectChat -> messages", () => { +describe("resolvers -> Chat -> messages", () => { it(`returns user object for parent.messages`, async () => { - const parent = testDirectChat?.toObject(); + const parent = testChat?.toObject(); if (!parent) { throw new Error("Parent object is undefined."); } const messagesPayload = await messagesResolver?.(parent, {}, {}); - const messages = await DirectChatMessage.find({ + const messages = await ChatMessage.find({ _id: { - $in: testDirectChat?.messages, + $in: testChat?.messages, }, }).lean(); diff --git a/tests/resolvers/DirectChat/organization.spec.ts b/tests/resolvers/Chat/organization.spec.ts similarity index 61% rename from tests/resolvers/DirectChat/organization.spec.ts rename to tests/resolvers/Chat/organization.spec.ts index 70d8c994762..f8414b6a749 100644 --- a/tests/resolvers/DirectChat/organization.spec.ts +++ b/tests/resolvers/Chat/organization.spec.ts @@ -1,28 +1,28 @@ import "dotenv/config"; +import { organization as organizationResolver } from "../../../src/resolvers/Chat/organization"; +import { connect, disconnect } from "../../helpers/db"; import type mongoose from "mongoose"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { Organization } from "../../../src/models"; -import { organization as organizationResolver } from "../../../src/resolvers/DirectChat/organization"; -import { connect, disconnect } from "../../helpers/db"; -import type { TestDirectChatType } from "../../helpers/directChat"; -import { createTestDirectChat } from "../../helpers/directChat"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import type { TestChatType } from "../../helpers/chat"; +import { createTestChatMessage } from "../../helpers/chat"; -let testDirectChat: TestDirectChatType; +let testChat: TestChatType; let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const userOrgChat = await createTestDirectChat(); - testDirectChat = userOrgChat[2]; + const userOrgChat = await createTestChatMessage(); + testChat = userOrgChat[2]; }); afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> DirectChat -> organization", () => { - it(`returns user object for parent.organization`, async () => { - const parent = testDirectChat?.toObject(); +describe("resolvers -> Chat -> organization", () => { + it(`resolves the correct organization for the given chat`, async () => { + const parent = testChat?.toObject(); if (!parent) { throw new Error("Parent object is undefined."); } @@ -30,22 +30,22 @@ describe("resolvers -> DirectChat -> organization", () => { const organizationPayload = await organizationResolver?.(parent, {}, {}); const organization = await Organization.findOne({ - _id: testDirectChat?.organization, + _id: testChat?.organization, }).lean(); expect(organizationPayload).toEqual(organization); }); - - it(`returns user object for parent.organization`, async () => { - const parent = testDirectChat?.toObject(); + it("resolves the organization from cache", async () => { + const parent = testChat?.toObject(); if (!parent) { throw new Error("Parent object is undefined."); } + // Simulate the cache resolution logic here (if applicable) const organizationPayload = await organizationResolver?.(parent, {}, {}); const organization = await Organization.findOne({ - _id: testDirectChat?.organization, + _id: testChat?.organization, }).lean(); expect(organizationPayload).toEqual(organization); diff --git a/tests/resolvers/DirectChat/users.spec.ts b/tests/resolvers/Chat/users.spec.ts similarity index 59% rename from tests/resolvers/DirectChat/users.spec.ts rename to tests/resolvers/Chat/users.spec.ts index d30e6fa4cdc..541b02b497b 100644 --- a/tests/resolvers/DirectChat/users.spec.ts +++ b/tests/resolvers/Chat/users.spec.ts @@ -1,28 +1,28 @@ import "dotenv/config"; -import { users as usersResolver } from "../../../src/resolvers/DirectChat/users"; +import { users as usersResolver } from "../../../src/resolvers/Chat/users"; import { connect, disconnect } from "../../helpers/db"; import type mongoose from "mongoose"; import { User } from "../../../src/models"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import type { TestDirectChatType } from "../../helpers/directChat"; -import { createTestDirectChatMessage } from "../../helpers/directChat"; +import type { TestChatType } from "../../helpers/chat"; +import { createTestChatMessage } from "../../helpers/chat"; -let testDirectChat: TestDirectChatType; +let testChat: TestChatType; let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const userOrgChat = await createTestDirectChatMessage(); - testDirectChat = userOrgChat[2]; + const userOrgChat = await createTestChatMessage(); + testChat = userOrgChat[2]; }); afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> DirectChat -> users", () => { +describe("resolvers -> Chat -> users", () => { it(`returns user object for parent.users`, async () => { - const parent = testDirectChat?.toObject(); + const parent = testChat?.toObject(); if (!parent) { throw new Error("Parent object is undefined."); } @@ -31,7 +31,7 @@ describe("resolvers -> DirectChat -> users", () => { const users = await User.find({ _id: { - $in: testDirectChat?.users, + $in: testChat?.users, }, }).lean(); diff --git a/tests/resolvers/ChatMessage/chatMessageBelongsTo.spec.ts b/tests/resolvers/ChatMessage/chatMessageBelongsTo.spec.ts new file mode 100644 index 00000000000..bba2b832f59 --- /dev/null +++ b/tests/resolvers/ChatMessage/chatMessageBelongsTo.spec.ts @@ -0,0 +1,76 @@ +import "dotenv/config"; +import { chatMessageBelongsTo as chatMessageBelongsToResolver } from "../../../src/resolvers/ChatMessage/chatMessageBelongsTo"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { Chat } from "../../../src/models"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { TestChatMessageType } from "../../helpers/chat"; +import { createTestChatMessage } from "../../helpers/chat"; +import { Types } from "mongoose"; +import { CHAT_NOT_FOUND_ERROR } from "../../../src/constants"; + +let testChatMessage: TestChatMessageType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const temp = await createTestChatMessage(); + testChatMessage = temp[3]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> ChatMessage -> chatMessageBelongsTo", () => { + it(`returns chat object for parent.chatMessageBelongsTo`, async () => { + const parent = testChatMessage?.toObject(); + + if (!parent) { + throw new Error("Parent object is undefined."); + } + + if (typeof chatMessageBelongsToResolver !== "function") { + throw new Error("chatMessageBelongsToResolver is not a function."); + } + + const chatMessageBelongsToPayload = await chatMessageBelongsToResolver( + parent, + {}, + {}, + ); + + const chatMessageBelongsTo = await Chat.findOne({ + _id: testChatMessage?.chatMessageBelongsTo, + }).lean(); + + expect(chatMessageBelongsToPayload).toEqual(chatMessageBelongsTo); + }); + it(`throws NotFoundError if no chat exists`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message: string) => message); + + const parent = { + ...testChatMessage?.toObject(), + chatMessageBelongsTo: new Types.ObjectId(), // Set to a non-existing ObjectId + }; + + if (!parent) { + throw new Error("Parent object is undefined."); + } + + if (typeof chatMessageBelongsToResolver !== "function") { + throw new Error("chatMessageBelongsToResolver is not a function."); + } + + try { + // @ts-expect-error - Testing for error + await chatMessageBelongsToResolver(parent, {}, {}); + } catch (error: unknown) { + expect(spy).toBeCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); + expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); + } + }); +}); diff --git a/tests/resolvers/ChatMessage/replyTo.spec.ts b/tests/resolvers/ChatMessage/replyTo.spec.ts new file mode 100644 index 00000000000..585fd09b7ee --- /dev/null +++ b/tests/resolvers/ChatMessage/replyTo.spec.ts @@ -0,0 +1,119 @@ +import "dotenv/config"; +import { replyTo as replyToResolver } from "../../../src/resolvers/ChatMessage/replyTo"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { ChatMessage } from "../../../src/models"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { TestChatMessageType } from "../../helpers/chat"; +import { + createTestChatMessageWithoutReply, + createTestChatMessage, +} from "../../helpers/chat"; +import { Types } from "mongoose"; +import { MESSAGE_NOT_FOUND_ERROR } from "../../../src/constants"; + +let testChatMessage: TestChatMessageType; +let testChatMessageWithoutReply: TestChatMessageType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + try { + MONGOOSE_INSTANCE = await connect(); + } catch (error) { + console.error("Failed to connect to the database", error); + throw error; + } + + try { + const temp1 = await createTestChatMessage(); + testChatMessage = temp1[3]; + } catch (error) { + console.error("Failed to create test chat message", error); + throw error; + } + + try { + const temp = await createTestChatMessageWithoutReply(); + testChatMessageWithoutReply = temp[3]; + } catch (error) { + console.error("Failed to create test chat message without reply", error); + throw error; + } +}); + +afterAll(async () => { + if (MONGOOSE_INSTANCE) { + await disconnect(MONGOOSE_INSTANCE); + } +}); + +describe("resolvers -> ChatMessage -> replyTo", () => { + it(`returns chat object for parent.replyTo`, async () => { + const parent = testChatMessage ? testChatMessage.toObject() : null; + + if (!parent) { + throw new Error("Parent object is undefined."); + } + + if (typeof replyToResolver !== "function") { + throw new Error("replyToResolver is not a function."); + } + + try { + const replyToPayload = await replyToResolver(parent, {}, {}); + const replyTo = await ChatMessage.findOne({ + _id: testChatMessage?.replyTo, + }).lean(); + expect(replyToPayload).toEqual(replyTo); + } catch (error) { + console.error("Test failed", error); + throw error; + } + }); + it(`throws NotFoundError if no chat exists`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message: string) => message); + + const parent = { + ...testChatMessage?.toObject(), + replyTo: new Types.ObjectId(), // Set to a non-existing ObjectId + }; + + if (!parent) { + throw new Error("Parent object is undefined."); + } + + if (typeof replyToResolver !== "function") { + throw new Error("replyToResolver is not a function."); + } + + try { + // @ts-expect-error - Testing for error + await replyToResolver(parent, {}, {}); + } catch (error: unknown) { + expect(spy).toBeCalledWith(MESSAGE_NOT_FOUND_ERROR.MESSAGE); + expect((error as Error).message).toEqual(MESSAGE_NOT_FOUND_ERROR.MESSAGE); + } finally { + spy.mockRestore(); // Restore the original function + } + }); + it(`return null if no replyTo`, async () => { + const parent = testChatMessageWithoutReply?.toObject(); + + if (!parent) { + throw new Error("Parent object is undefined."); + } + + if (typeof replyToResolver !== "function") { + throw new Error("replyToResolver is not a function."); + } + + const replyToPayload = await replyToResolver(parent, {}, {}); + + const replyTo = null; + + expect(replyToPayload).toEqual(replyTo); + }); +}); diff --git a/tests/resolvers/DirectChatMessage/sender.spec.ts b/tests/resolvers/ChatMessage/sender.spec.ts similarity index 53% rename from tests/resolvers/DirectChatMessage/sender.spec.ts rename to tests/resolvers/ChatMessage/sender.spec.ts index 399de175795..8c64c91c082 100644 --- a/tests/resolvers/DirectChatMessage/sender.spec.ts +++ b/tests/resolvers/ChatMessage/sender.spec.ts @@ -1,37 +1,37 @@ import "dotenv/config"; -import { sender as senderResolver } from "../../../src/resolvers/DirectChatMessage/sender"; +import { sender as senderResolver } from "../../../src/resolvers/ChatMessage/sender"; import { connect, disconnect } from "../../helpers/db"; import type mongoose from "mongoose"; import { User } from "../../../src/models"; import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; -import type { TestDirectChatMessageType } from "../../helpers/directChat"; -import { createTestDirectChatMessage } from "../../helpers/directChat"; +import type { TestChatMessageType } from "../../helpers/chat"; +import { createTestChatMessage } from "../../helpers/chat"; import { Types } from "mongoose"; import { USER_NOT_FOUND_ERROR } from "../../../src/constants"; -let testDirectChatMessage: TestDirectChatMessageType; +let testChatMessage: TestChatMessageType; let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const temp = await createTestDirectChatMessage(); - testDirectChatMessage = temp[3]; + const temp = await createTestChatMessage(); + testChatMessage = temp[3]; }); afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> DirectChatMessage -> sender", () => { +describe("resolvers -> ChatMessage -> sender", () => { it(`returns user object for parent.sender`, async () => { - const parent = testDirectChatMessage?.toObject(); + const parent = testChatMessage?.toObject(); if (!parent) { throw new Error("Parent object is undefined."); } const senderPayload = await senderResolver?.(parent, {}, {}); const sender = await User.findOne({ - _id: testDirectChatMessage?.sender, + _id: testChatMessage?.sender, }).lean(); expect(senderPayload).toEqual(sender); @@ -41,23 +41,25 @@ describe("resolvers -> DirectChatMessage -> sender", () => { const spy = vi .spyOn(requestContext, "translate") .mockImplementationOnce((message) => message); - const parent = { - ...testDirectChatMessage?.toObject(), - sender: new Types.ObjectId(), // Set to a non-existing ObjectId - }; + if (testChatMessage?._id) { + const parent = { + ...testChatMessage?.toObject(), + _id: testChatMessage._id, + sender: new Types.ObjectId(), // Set to a non-existing ObjectId + }; - if (!parent) { - throw new Error("Parent object is undefined."); - } + if (!parent) { + throw new Error("Parent object is undefined."); + } - try { - if (senderResolver) { - // @ts-expect-error - Testing for error - await senderResolver(parent, {}, {}); + try { + if (senderResolver) { + await senderResolver(parent, {}, {}); + } + } catch (error: unknown) { + expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); + expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); } - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); } }); }); diff --git a/tests/resolvers/DirectChatMessage/directChatMessageBelongsTo.spec.ts b/tests/resolvers/DirectChatMessage/directChatMessageBelongsTo.spec.ts deleted file mode 100644 index 5ff19d843e1..00000000000 --- a/tests/resolvers/DirectChatMessage/directChatMessageBelongsTo.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import "dotenv/config"; -import { directChatMessageBelongsTo as directChatMessageBelongsToResolver } from "../../../src/resolvers/DirectChatMessage/directChatMessageBelongsTo"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { DirectChat } from "../../../src/models"; -import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; -import type { TestDirectChatMessageType } from "../../helpers/directChat"; -import { createTestDirectChatMessage } from "../../helpers/directChat"; -import { Types } from "mongoose"; -import { CHAT_NOT_FOUND_ERROR } from "../../../src/constants"; - -let testDirectChatMessage: TestDirectChatMessageType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestDirectChatMessage(); - testDirectChatMessage = temp[3]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> DirectChatMessage -> directChatMessageBelongsTo", () => { - it(`returns directChat object for parent.directChatMessageBelongsTo`, async () => { - const parent = testDirectChatMessage?.toObject(); - - if (!parent) { - throw new Error("Parent object is undefined."); - } - - if (typeof directChatMessageBelongsToResolver !== "function") { - throw new Error("directChatMessageBelongsToResolver is not a function."); - } - - const directChatMessageBelongsToPayload = - await directChatMessageBelongsToResolver(parent, {}, {}); - - const directChatMessageBelongsTo = await DirectChat.findOne({ - _id: testDirectChatMessage?.directChatMessageBelongsTo, - }).lean(); - - expect(directChatMessageBelongsToPayload).toEqual( - directChatMessageBelongsTo, - ); - }); - it(`throws NotFoundError if no directChat exists`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - - const parent = { - ...testDirectChatMessage?.toObject(), - directChatMessageBelongsTo: new Types.ObjectId(), // Set to a non-existing ObjectId - }; - - if (!parent) { - throw new Error("Parent object is undefined."); - } - - if (typeof directChatMessageBelongsToResolver !== "function") { - throw new Error("directChatMessageBelongsToResolver is not a function."); - } - - try { - // @ts-expect-error - Testing for error - await directChatMessageBelongsToResolver(parent, {}, {}); - } catch (error: unknown) { - expect(spy).toBeCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); - } - }); -}); diff --git a/tests/resolvers/DirectChatMessage/receiver.spec.ts b/tests/resolvers/DirectChatMessage/receiver.spec.ts deleted file mode 100644 index 9c39f6b18ba..00000000000 --- a/tests/resolvers/DirectChatMessage/receiver.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import "dotenv/config"; -import { receiver as receiverResolver } from "../../../src/resolvers/DirectChatMessage/receiver"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { User } from "../../../src/models"; -import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; -import type { TestDirectChatMessageType } from "../../helpers/directChat"; -import { createTestDirectChatMessage } from "../../helpers/directChat"; -import { Types } from "mongoose"; -import { USER_NOT_FOUND_ERROR } from "../../../src/constants"; - -let testDirectChatMessage: TestDirectChatMessageType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestDirectChatMessage(); - testDirectChatMessage = temp[3]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> DirectChatMessage -> receiver", () => { - it(`returns user object for parent.receiver`, async () => { - const parent = testDirectChatMessage?.toObject(); - if (!parent) { - throw new Error("Parent object is undefined."); - } - const receiverPayload = await receiverResolver?.(parent, {}, {}); - - const receiver = await User.findOne({ - _id: testDirectChatMessage?.receiver, - }).lean(); - - expect(receiverPayload).toEqual(receiver); - }); - it(`throws NotFoundError if no user exists`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - - const parent = { - ...testDirectChatMessage?.toObject(), - receiver: new Types.ObjectId(), // Set to a non-existing ObjectId - }; - - if (!parent) { - throw new Error("Parent object is undefined."); - } - - try { - if (receiverResolver) { - // @ts-expect-error - Testing for error - await receiverResolver(parent, {}, {}); - } - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); -}); diff --git a/tests/resolvers/Event/actionItems.spec.ts b/tests/resolvers/Event/actionItems.spec.ts index 716f511d964..ad06fbb673a 100644 --- a/tests/resolvers/Event/actionItems.spec.ts +++ b/tests/resolvers/Event/actionItems.spec.ts @@ -26,7 +26,7 @@ describe("resolvers -> Organization -> actionItems", () => { const actionItemsPayload = await actionItemsResolver?.(parent, {}, {}); const actionItems = await ActionItem.find({ - eventId: testEvent?._id, + event: testEvent?._id, }).lean(); expect(actionItemsPayload).toEqual(actionItems); diff --git a/tests/resolvers/EventVolunteer/creator.spec.ts b/tests/resolvers/EventVolunteer/creator.spec.ts deleted file mode 100644 index 88e07a87229..00000000000 --- a/tests/resolvers/EventVolunteer/creator.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import "dotenv/config"; -import { creator as creatorResolver } from "../../../src/resolvers/EventVolunteer/creator"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { - beforeAll, - afterAll, - describe, - it, - expect, - beforeEach, - vi, -} from "vitest"; -import type { TestEventVolunteerType } from "../../helpers/events"; -import { createTestEventAndVolunteer } from "../../helpers/events"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import type { InterfaceEventVolunteer } from "../../../src/models"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testEventVolunteer: TestEventVolunteerType; -let creatorUser: TestUserType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [, creatorUser, , testEventVolunteer] = await createTestEventAndVolunteer(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> EventVolunteer -> creator", () => { - beforeEach(() => { - vi.resetModules(); - }); - it(`returns the correct creator object for parent event volunteer`, async () => { - const parent = testEventVolunteer?.toObject(); - const creatorPayload = await creatorResolver?.( - parent as InterfaceEventVolunteer, - {}, - {}, - ); - - expect(creatorPayload?._id).toEqual(creatorUser?._id); - }); -}); diff --git a/tests/resolvers/EventVolunteer/event.spec.ts b/tests/resolvers/EventVolunteer/event.spec.ts deleted file mode 100644 index 7a0b04ab5b2..00000000000 --- a/tests/resolvers/EventVolunteer/event.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import "dotenv/config"; -import { event as eventResolver } from "../../../src/resolvers/EventVolunteer/event"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { - TestEventType, - TestEventVolunteerType, -} from "../../helpers/events"; -import { createTestEventAndVolunteer } from "../../helpers/events"; -import type { InterfaceEventVolunteer } from "../../../src/models"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testEvent: TestEventType; -let testEventVolunteer: TestEventVolunteerType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [, , testEvent, testEventVolunteer] = await createTestEventAndVolunteer(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> EventVolunteer -> event", () => { - it(`returns the correct event object for parent event volunteer`, async () => { - const parent = testEventVolunteer?.toObject(); - - const eventPayload = await eventResolver?.( - parent as InterfaceEventVolunteer, - {}, - {}, - ); - - expect(eventPayload).toEqual(testEvent?.toObject()); - }); -}); diff --git a/tests/resolvers/EventVolunteer/group.spec.ts b/tests/resolvers/EventVolunteer/group.spec.ts deleted file mode 100644 index 856641b6247..00000000000 --- a/tests/resolvers/EventVolunteer/group.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import "dotenv/config"; -import { group as groupResolver } from "../../../src/resolvers/EventVolunteer/group"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { - beforeAll, - afterAll, - describe, - it, - expect, - beforeEach, - vi, -} from "vitest"; -import type { - TestEventType, - TestEventVolunteerType, -} from "../../helpers/events"; -import { createTestEvent } from "../../helpers/events"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import type { InterfaceEventVolunteer } from "../../../src/models"; -import { EventVolunteer, EventVolunteerGroup } from "../../../src/models"; -import type { TestEventVolunteerGroupType } from "../Mutation/createEventVolunteer.spec"; -import { createTestUser } from "../../helpers/user"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testEventVolunteer: TestEventVolunteerType; -let eventAdminUser: TestUserType; -let testUser: TestUserType; -let testEvent: TestEventType; -let testGroup: TestEventVolunteerGroupType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [eventAdminUser, , testEvent] = await createTestEvent(); - testUser = await createTestUser(); - testGroup = await EventVolunteerGroup.create({ - name: "test", - creatorId: eventAdminUser?._id, - leaderId: eventAdminUser?._id, - eventId: testEvent?._id, - }); - testEventVolunteer = await EventVolunteer.create({ - eventId: testEvent?._id, - userId: testUser?._id, - creatorId: eventAdminUser?._id, - groupId: testGroup?._id, - isAssigned: false, - isInvited: true, - }); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> EventVolunteer -> group", () => { - beforeEach(() => { - vi.resetModules(); - }); - it(`returns the correct event volunteer group object for parent event volunteer`, async () => { - const parent = testEventVolunteer?.toObject(); - const groupPayload = await groupResolver?.( - parent as InterfaceEventVolunteer, - {}, - {}, - ); - console.log(groupPayload); - console.log(testGroup); - - expect(groupPayload?._id).toEqual(testGroup?._id); - }); -}); diff --git a/tests/resolvers/EventVolunteer/user.spec.ts b/tests/resolvers/EventVolunteer/user.spec.ts deleted file mode 100644 index 56ac382f6b2..00000000000 --- a/tests/resolvers/EventVolunteer/user.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import "dotenv/config"; -import { user as userResolver } from "../../../src/resolvers/EventVolunteer/user"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { - beforeAll, - afterAll, - describe, - it, - expect, - beforeEach, - vi, -} from "vitest"; -import type { TestEventVolunteerType } from "../../helpers/events"; -import { createTestEventAndVolunteer } from "../../helpers/events"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import type { InterfaceEventVolunteer } from "../../../src/models"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testEventVolunteer: TestEventVolunteerType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [testUser, , , testEventVolunteer] = await createTestEventAndVolunteer(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> EventVolunteer -> user", () => { - beforeEach(() => { - vi.resetModules(); - }); - it(`returns the correct user object for parent event volunteer`, async () => { - const parent = testEventVolunteer?.toObject(); - console.log(testEventVolunteer?.userId); - console.log(testUser?._id); - - const userPayload = await userResolver?.( - parent as InterfaceEventVolunteer, - {}, - {}, - ); - - expect(userPayload).toEqual({ - ...testUser?.toObject(), - updatedAt: expect.anything(), - }); - }); -}); diff --git a/tests/resolvers/EventVolunteerGroup/creator.spec.ts b/tests/resolvers/EventVolunteerGroup/creator.spec.ts deleted file mode 100644 index e9bd6502bff..00000000000 --- a/tests/resolvers/EventVolunteerGroup/creator.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import "dotenv/config"; -import { creator as creatorResolver } from "../../../src/resolvers/EventVolunteerGroup/creator"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestEventType } from "../../helpers/events"; -import { createTestEvent } from "../../helpers/events"; -import type { InterfaceEventVolunteerGroup } from "../../../src/models"; -import { EventVolunteerGroup } from "../../../src/models"; -import type { TestUserType } from "../../helpers/user"; -import type { TestEventVolunteerGroupType } from "../Mutation/createEventVolunteer.spec"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let eventAdminUser: TestUserType; -let testEvent: TestEventType; -let testGroup: TestEventVolunteerGroupType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [eventAdminUser, , testEvent] = await createTestEvent(); - testGroup = await EventVolunteerGroup.create({ - name: "test", - creatorId: eventAdminUser?._id, - leaderId: eventAdminUser?._id, - eventId: testEvent?._id, - }); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> EventVolunteer -> creator", () => { - it(`returns the correct creator user object for parent event volunteer group`, async () => { - const parent = testGroup?.toObject(); - - const creatorPayload = await creatorResolver?.( - parent as InterfaceEventVolunteerGroup, - {}, - {}, - ); - - expect(creatorPayload?._id).toEqual(eventAdminUser?._id); - }); -}); diff --git a/tests/resolvers/EventVolunteerGroup/event.spec.ts b/tests/resolvers/EventVolunteerGroup/event.spec.ts deleted file mode 100644 index 5cfaeb7450b..00000000000 --- a/tests/resolvers/EventVolunteerGroup/event.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import "dotenv/config"; -import { event as eventResolver } from "../../../src/resolvers/EventVolunteerGroup/event"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestEventType } from "../../helpers/events"; -import { createTestEvent } from "../../helpers/events"; -import type { InterfaceEventVolunteerGroup } from "../../../src/models"; -import { EventVolunteerGroup } from "../../../src/models"; -import type { TestUserType } from "../../helpers/user"; -import type { TestEventVolunteerGroupType } from "../Mutation/createEventVolunteer.spec"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let eventAdminUser: TestUserType; -let testEvent: TestEventType; -let testGroup: TestEventVolunteerGroupType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [eventAdminUser, , testEvent] = await createTestEvent(); - testGroup = await EventVolunteerGroup.create({ - name: "test", - creatorId: eventAdminUser?._id, - leaderId: eventAdminUser?._id, - eventId: testEvent?._id, - }); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> EventVolunteer -> event", () => { - it(`returns the correct event object for parent event volunteer group`, async () => { - const parent = testGroup?.toObject(); - - const eventPayload = await eventResolver?.( - parent as InterfaceEventVolunteerGroup, - {}, - {}, - ); - - expect(eventPayload).toEqual(testEvent?.toObject()); - }); -}); diff --git a/tests/resolvers/EventVolunteerGroup/leader.spec.ts b/tests/resolvers/EventVolunteerGroup/leader.spec.ts deleted file mode 100644 index 07481e41b55..00000000000 --- a/tests/resolvers/EventVolunteerGroup/leader.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import "dotenv/config"; -import { leader as leaderResolver } from "../../../src/resolvers/EventVolunteerGroup/leader"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestEventType } from "../../helpers/events"; -import { createTestEvent } from "../../helpers/events"; -import type { InterfaceEventVolunteerGroup } from "../../../src/models"; -import { EventVolunteerGroup } from "../../../src/models"; -import type { TestUserType } from "../../helpers/user"; -import type { TestEventVolunteerGroupType } from "../Mutation/createEventVolunteer.spec"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let eventAdminUser: TestUserType; -let testEvent: TestEventType; -let testGroup: TestEventVolunteerGroupType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [eventAdminUser, , testEvent] = await createTestEvent(); - testGroup = await EventVolunteerGroup.create({ - name: "test", - creatorId: eventAdminUser?._id, - leaderId: eventAdminUser?._id, - eventId: testEvent?._id, - }); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> EventVolunteer -> leader", () => { - it(`returns the correct leader user object for parent event volunteer group`, async () => { - const parent = testGroup?.toObject(); - - const leaderPayload = await leaderResolver?.( - parent as InterfaceEventVolunteerGroup, - {}, - {}, - ); - - expect(leaderPayload?._id).toEqual(eventAdminUser?._id); - }); -}); diff --git a/tests/resolvers/GroupChat/creator.spec.ts b/tests/resolvers/GroupChat/creator.spec.ts deleted file mode 100644 index 5184e618d44..00000000000 --- a/tests/resolvers/GroupChat/creator.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import "dotenv/config"; -import { creator as creatorResolver } from "../../../src/resolvers/GroupChat/creator"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import type { InterfaceGroupChat } from "../../../src/models"; -import { User } from "../../../src/models"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChat } from "../../helpers/groupChat"; - -let testGroupChat: TestGroupChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChat(); - testGroupChat = resultArray[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> GroupChat -> creator", () => { - it(`returns user object for parent.creator`, async () => { - const parent = testGroupChat?.toObject(); - - const creatorPayload = await creatorResolver?.( - parent ?? ({} as InterfaceGroupChat), - {}, - {}, - ); - - const creator = await User.findOne({ - _id: testGroupChat?.creatorId, - }).lean(); - - expect(creatorPayload).toEqual(creator); - }); -}); diff --git a/tests/resolvers/GroupChat/messages.spec.ts b/tests/resolvers/GroupChat/messages.spec.ts deleted file mode 100644 index 5f69793f0e3..00000000000 --- a/tests/resolvers/GroupChat/messages.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import "dotenv/config"; -import { messages as messagesResolver } from "../../../src/resolvers/GroupChat/messages"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { GroupChatMessage } from "../../../src/models"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChat } from "../../helpers/groupChat"; - -let testGroupChat: TestGroupChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChat(); - testGroupChat = resultArray[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> GroupChat -> messages", () => { - it(`returns user objects for parent.messages`, async () => { - const parent = testGroupChat?.toObject(); - if (!parent) { - throw new Error("Parent object is undefined."); - } - const messagesPayload = await messagesResolver?.(parent, {}, {}); - - const messages = await GroupChatMessage.find({ - _id: { - $in: testGroupChat?.messages, - }, - }).lean(); - - expect(messagesPayload).toEqual(messages); - }); -}); diff --git a/tests/resolvers/GroupChat/organization.spec.ts b/tests/resolvers/GroupChat/organization.spec.ts deleted file mode 100644 index 2cb0baee865..00000000000 --- a/tests/resolvers/GroupChat/organization.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import "dotenv/config"; -import { organization as organizationResolver } from "../../../src/resolvers/GroupChat/organization"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { Organization } from "../../../src/models"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChat } from "../../helpers/groupChat"; - -let testGroupChat: TestGroupChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChat(); - testGroupChat = resultArray[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> GroupChat -> organization", () => { - it(`returns user objects for parent.organization`, async () => { - const parent = testGroupChat?.toObject(); - if (parent) { - const organizationPayload = await organizationResolver?.(parent, {}, {}); - const organization = await Organization.findOne({ - _id: testGroupChat?.organization, - }).lean(); - - expect(organizationPayload).toEqual(organization); - } - }); - it(`returns user objects for parent.organization from cache`, async () => { - const parent = testGroupChat?.toObject(); - if (parent) { - const organizationPayload = await organizationResolver?.(parent, {}, {}); - const organization = await Organization.findOne({ - _id: testGroupChat?.organization, - }).lean(); - - expect(organizationPayload).toEqual(organization); - } - }); -}); diff --git a/tests/resolvers/GroupChat/users.spec.ts b/tests/resolvers/GroupChat/users.spec.ts deleted file mode 100644 index 8616ca5e1ae..00000000000 --- a/tests/resolvers/GroupChat/users.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import "dotenv/config"; -import { users as usersResolver } from "../../../src/resolvers/GroupChat/users"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { User } from "../../../src/models"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChat } from "../../helpers/groupChat"; - -let testGroupChat: TestGroupChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChat(); - testGroupChat = resultArray[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> GroupChat -> users", () => { - it(`returns user objects for parent.users`, async () => { - const parent = testGroupChat?.toObject(); - if (!parent) { - throw new Error("Parent object is undefined."); - } - const usersPayload = await usersResolver?.(parent, {}, {}); - - const users = await User.find({ - _id: { - $in: testGroupChat?.users, - }, - }).lean(); - - expect(usersPayload).toEqual(users); - }); -}); diff --git a/tests/resolvers/GroupChatMessage/groupChatMessageBelongsTo.spec.ts b/tests/resolvers/GroupChatMessage/groupChatMessageBelongsTo.spec.ts deleted file mode 100644 index 08ade6fde53..00000000000 --- a/tests/resolvers/GroupChatMessage/groupChatMessageBelongsTo.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import "dotenv/config"; -import { groupChatMessageBelongsTo as groupChatMessageBelongsToResolver } from "../../../src/resolvers/GroupChatMessage/groupChatMessageBelongsTo"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { GroupChat } from "../../../src/models"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import type { TestGroupChatMessageType } from "../../helpers/groupChat"; -import { createTestGroupChatMessage } from "../../helpers/groupChat"; -import { CHAT_NOT_FOUND_ERROR } from "../../../src/constants"; - -let testGroupChatMessage: TestGroupChatMessageType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChatMessage(); - testGroupChatMessage = resultArray[3]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> GroupChatMessage -> groupChatMessageBelongsTo", () => { - it(`returns groupChatMessageBelongsTo object for parent.groupChatMessageBelongsTo`, async () => { - const parent = testGroupChatMessage?.toObject(); - if (!parent) { - throw new Error("Parent object is undefined."); - } - const groupChatMessageBelongsToPayload = - await groupChatMessageBelongsToResolver?.(parent, {}, {}); - - const groupChatMessageBelongsTo = await GroupChat.findOne({ - _id: testGroupChatMessage?.groupChatMessageBelongsTo, - }).lean(); - - expect(groupChatMessageBelongsToPayload).toEqual(groupChatMessageBelongsTo); - }); - it(`throws NotFoundError if no groupChatMessageBelongsTo exists`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - - const parent = { - ...testGroupChatMessage?.toObject(), - groupChatMessageBelongsTo: new Types.ObjectId(), // Set to a non-existing ObjectId - }; - - if (!parent) { - throw new Error("Parent object is undefined."); - } - - try { - if (groupChatMessageBelongsToResolver) { - // @ts-expect-error - Testing for error - await groupChatMessageBelongsToResolver(parent, {}, {}); - } - } catch (error: unknown) { - expect(spy).toBeCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); - } - }); -}); diff --git a/tests/resolvers/GroupChatMessage/sender.spec.ts b/tests/resolvers/GroupChatMessage/sender.spec.ts deleted file mode 100644 index d9c335d4e25..00000000000 --- a/tests/resolvers/GroupChatMessage/sender.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import "dotenv/config"; -import { sender as senderResolver } from "../../../src/resolvers/GroupChatMessage/sender"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { User } from "../../../src/models"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import type { TestGroupChatMessageType } from "../../helpers/groupChat"; -import { createTestGroupChatMessage } from "../../helpers/groupChat"; -import { Types } from "mongoose"; -import { USER_NOT_FOUND_ERROR } from "../../../src/constants"; - -let testGroupChatMessage: TestGroupChatMessageType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChatMessage(); - testGroupChatMessage = resultArray[3]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> GroupChatMessage -> sender", () => { - it(`returns sender object for parent.sender`, async () => { - const parent = testGroupChatMessage?.toObject(); - if (!parent) { - throw new Error("Parent object is undefined."); - } - const senderPayload = await senderResolver?.(parent, {}, {}); - - const sender = await User.findOne({ - _id: testGroupChatMessage?.sender, - }).lean(); - - expect(senderPayload).toEqual(sender); - }); - it(`throws NotFoundError if no user exists`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - - const parent = { - ...testGroupChatMessage?.toObject(), - sender: new Types.ObjectId(), // Set to a non-existing ObjectId - }; - - if (!parent) { - throw new Error("Parent object is undefined."); - } - - try { - if (senderResolver) { - // @ts-expect-error - Testing for error - await senderResolver(parent, {}, {}); - } - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); -}); diff --git a/tests/resolvers/Mutation/UpdateSessionTimeout.spec.ts b/tests/resolvers/Mutation/UpdateSessionTimeout.spec.ts new file mode 100644 index 00000000000..25ebe8855e5 --- /dev/null +++ b/tests/resolvers/Mutation/UpdateSessionTimeout.spec.ts @@ -0,0 +1,236 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; + +import { User, AppUserProfile, Community } from "../../../src/models"; +import type { MutationUpdateSessionTimeoutArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { nanoid } from "nanoid"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import { + COMMUNITY_NOT_FOUND_ERROR, + INVALID_TIMEOUT_RANGE, + USER_NOT_FOUND_ERROR, + APP_USER_PROFILE_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_SUPERADMIN, +} from "../../../src/constants"; +import { updateSessionTimeout as updateSessionTimeoutResolver } from "../../../src/resolvers/Mutation/updateSessionTimeout"; +import type { + TestAppUserProfileType, + TestUserType, +} from "../../helpers/userAndOrg"; + +import { requestContext } from "../../../src/libraries"; + +import bcrypt from "bcryptjs"; + +// Global variables to store mongoose instance and test user/appUserProfile +let MONGOOSE_INSTANCE: typeof mongoose; +let testUser: TestUserType; +let testAppUserProfile: TestAppUserProfileType; + +// Mock the uploadEncodedImage function used in the tests +vi.mock("../../utilities/uploadEncodedImage", () => ({ + uploadEncodedImage: vi.fn(), +})); + +// Mock bcrypt.hash to return a fixed value +vi.mock("bcrypt", () => ({ + hash: vi.fn().mockResolvedValue("mockedHashedPassword"), +})); + +/** + * Establishes a connection to the database before all tests. + */ +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); +}); + +/** + * Closes the database connection after all tests have completed. + */ +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +/** + * Sets up test data in the database before each test. + * Creates a test user and associated appUserProfile, and links them together. + * Also, creates a test community with a timeout value. + */ +beforeEach(async () => { + const hashedPassword = await bcrypt.hash("password", 12); + + testUser = await User.create({ + email: `email${nanoid().toLowerCase()}@gmail.com`, + password: hashedPassword, + firstName: "firstName", + lastName: "lastName", + }); + + testAppUserProfile = await AppUserProfile.create({ + userId: testUser._id, + appLanguageCode: "en", + tokenVersion: 0, + isSuperAdmin: true, + }); + + testUser.appUserProfileId = testAppUserProfile._id; + await testUser.save(); // Directly save the update to testUser + + await Community.create({ + name: "test community", + timeout: 25, + }); +}); + +/** + * Restores all mocks and resets modules after each test. + */ +afterEach(() => { + vi.restoreAllMocks(); + vi.doUnmock("../../../src/constants"); + vi.resetModules(); +}); + +/** + * Helper function to handle error assertions. + * @param resolverFunc - The resolver function to call + * @param args - The mutation arguments + * @param context - The request context + * @param expectedErrorMessage - The expected error message for assertion + */ +const assertThrowsErrorWithMessage = async ( + resolverFunc: typeof updateSessionTimeoutResolver, + args: MutationUpdateSessionTimeoutArgs, + context: { userId: string | undefined }, + expectedErrorMessage: string, +): Promise => { + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => message); + + try { + await resolverFunc?.({}, args, context); + } catch (error: unknown) { + expect(spy).toHaveBeenCalledWith(expectedErrorMessage); + expect((error as Error).message).toEqual(expectedErrorMessage); + } +}; + +/** + * Test suite for the `updateSessionTimeout` resolver function. + */ +describe("resolvers -> Mutation -> updateSessionTimeout", () => { + it("returns error when attempting to update timeout for non-existent community", async () => { + const args: MutationUpdateSessionTimeoutArgs = { + timeout: 15, + }; + const context = { + userId: testUser?._id, + }; + + await Community.deleteMany({}).lean(); + + await assertThrowsErrorWithMessage( + updateSessionTimeoutResolver, + args, + context, + COMMUNITY_NOT_FOUND_ERROR.MESSAGE, + ); + }); + + it("returns error when attempting to update timeout for non-existent user", async () => { + const args: MutationUpdateSessionTimeoutArgs = { + timeout: 15, + }; + const context = { + userId: new Types.ObjectId().toString(), + }; + + await assertThrowsErrorWithMessage( + updateSessionTimeoutResolver, + args, + context, + USER_NOT_FOUND_ERROR.MESSAGE, + ); + }); + + it("returns error when appUserProfile is missing for the user", async () => { + const args: MutationUpdateSessionTimeoutArgs = { + timeout: 15, + }; + const context = { + userId: testUser?._id, + }; + + await AppUserProfile.deleteOne({ userId: testUser?._id }).lean(); + + await assertThrowsErrorWithMessage( + updateSessionTimeoutResolver, + args, + context, + APP_USER_PROFILE_NOT_FOUND_ERROR.MESSAGE, + ); + }); + + it("returns validation error for timeout out of valid range", async () => { + const args: MutationUpdateSessionTimeoutArgs = { + timeout: 3, + }; + const context = { + userId: testUser?._id, + }; + + await assertThrowsErrorWithMessage( + updateSessionTimeoutResolver, + args, + context, + INVALID_TIMEOUT_RANGE.MESSAGE, + ); + }); + + it("returns unauthorized error when superAdmin is set to false", async () => { + const args: MutationUpdateSessionTimeoutArgs = { + timeout: 15, + }; + const context = { + userId: testUser?._id, + }; + + await AppUserProfile.findByIdAndUpdate( + { _id: testUser?.appUserProfileId }, + { isSuperAdmin: false }, + ).lean(); + + await assertThrowsErrorWithMessage( + updateSessionTimeoutResolver, + args, + context, + USER_NOT_AUTHORIZED_SUPERADMIN.MESSAGE, + ); + }); + + it("successfully updates session timeout when valid arguments are provided", async () => { + const args: MutationUpdateSessionTimeoutArgs = { + timeout: 15, + }; + const context = { + userId: testUser?._id, + }; + + const result = await updateSessionTimeoutResolver?.({}, args, context); + + expect(result).toEqual(true); + }); +}); diff --git a/tests/resolvers/Mutation/addEventAttendee.spec.ts b/tests/resolvers/Mutation/addEventAttendee.spec.ts index a83a1d0d818..6e371ae0f11 100644 --- a/tests/resolvers/Mutation/addEventAttendee.spec.ts +++ b/tests/resolvers/Mutation/addEventAttendee.spec.ts @@ -10,7 +10,12 @@ import { USER_NOT_FOUND_ERROR, USER_NOT_MEMBER_FOR_ORGANIZATION, } from "../../../src/constants"; -import { AppUserProfile, EventAttendee, User } from "../../../src/models"; +import { + AppUserProfile, + Event, + EventAttendee, + User, +} from "../../../src/models"; import type { MutationAddEventAttendeeArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; import { createTestEvent, type TestEventType } from "../../helpers/events"; @@ -153,39 +158,41 @@ describe("resolvers -> Mutation -> addEventAttendee", () => { }); it(`registers the request user for the event successfully and returns the request user`, async () => { - const eventOrganizationId = testEvent?.organization._id; - const userId = randomTestUser?._id; - - await User.updateOne( - { _id: userId }, - { $addToSet: { joinedOrganizations: eventOrganizationId } }, - ); - - const args: MutationAddEventAttendeeArgs = { - data: { - userId: userId, - eventId: testEvent?._id ?? "", - }, - }; + if (testEvent?.organization) { + const eventOrganizationId = testEvent?.organization._id; + const userId = randomTestUser?._id; - const context = { userId: testUser?._id }; + await User.updateOne( + { _id: userId }, + { $addToSet: { joinedOrganizations: eventOrganizationId } }, + ); - const { addEventAttendee: addEventAttendeeResolver } = await import( - "../../../src/resolvers/Mutation/addEventAttendee" - ); - const payload = await addEventAttendeeResolver?.({}, args, context); + const args: MutationAddEventAttendeeArgs = { + data: { + userId: userId, + eventId: testEvent?._id.toString() ?? "", + }, + }; - const requestUser = await User.findOne({ - _id: userId?._id, - }).lean(); + const context = { userId: testUser?._id }; - const isUserRegistered = await EventAttendee.exists({ - ...args.data, - }); - expect(payload).toEqual(requestUser); - expect(isUserRegistered).toBeTruthy(); + const { addEventAttendee: addEventAttendeeResolver } = await import( + "../../../src/resolvers/Mutation/addEventAttendee" + ); + const payload = await addEventAttendeeResolver?.({}, args, context); + + expect(payload).toBeDefined(); + + const requestUser = await User.findOne({ + _id: userId?._id, + }).lean(); + expect(payload).toEqual(expect.objectContaining(requestUser)); + const isUserRegistered = await EventAttendee.exists({ + ...args.data, + }); + expect(isUserRegistered).toBeTruthy(); + } }); - it(`throws UnauthorizedError if the requestUser is not a member of the organization`, async () => { const { requestContext } = await import("../../../src/libraries"); @@ -199,11 +206,10 @@ describe("resolvers -> Mutation -> addEventAttendee", () => { ); try { - // Create a test user who is not a member of the organization const args: MutationAddEventAttendeeArgs = { data: { userId: testUser?._id, - eventId: testEvent!._id, + eventId: testEvent!._id.toString(), }, }; @@ -291,4 +297,93 @@ describe("resolvers -> Mutation -> addEventAttendee", () => { ); } }); + it("throws NotFoundError if the user is not found after update", async () => { + const { requestContext } = await import("../../../src/libraries"); + + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + const mockFindByIdAndUpdate = vi + .spyOn(User, "findByIdAndUpdate") + .mockResolvedValueOnce(null); + + const args: MutationAddEventAttendeeArgs = { + data: { + userId: testUser?._id, + eventId: testEvent?._id.toString() ?? "", + }, + }; + + const context = { userId: testUser?._id }; + + try { + const { addEventAttendee: addEventAttendeeResolver } = await import( + "../../../src/resolvers/Mutation/addEventAttendee" + ); + + await addEventAttendeeResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + ); + expect(spy).toHaveBeenLastCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); + } + mockFindByIdAndUpdate.mockRestore(); + }); + it("throws UnauthorizedError when user update fails", async () => { + const { requestContext } = await import("../../../src/libraries"); + + await AppUserProfile.create({ + userId: testUser?._id, + adminFor: [testEvent?.organization._id], + }); + + await Event.findByIdAndUpdate( + testEvent?._id, + { $pull: { admins: testUser?._id } }, + { new: true }, + ); + + await User.findByIdAndUpdate(testUser?._id, { + $push: { joinedOrganizations: testEvent?.organization._id }, + }); + + await EventAttendee.deleteOne({ + userId: testUser?._id, + eventId: testEvent?._id, + }); + + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + const mockFindByIdAndUpdate = vi + .spyOn(User, "findByIdAndUpdate") + .mockResolvedValueOnce(null); + + const args: MutationAddEventAttendeeArgs = { + data: { + userId: testUser?._id, + eventId: testEvent?._id.toString(), + }, + }; + + const context = { userId: testUser?._id }; + + try { + const { addEventAttendee: addEventAttendeeResolver } = await import( + "../../../src/resolvers/Mutation/addEventAttendee" + ); + + await addEventAttendeeResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + ); + expect(spy).toHaveBeenCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); + } + + mockFindByIdAndUpdate.mockRestore(); + }); }); diff --git a/tests/resolvers/Mutation/addPeopleToUserTag.spec.ts b/tests/resolvers/Mutation/addPeopleToUserTag.spec.ts new file mode 100644 index 00000000000..a8f07277c72 --- /dev/null +++ b/tests/resolvers/Mutation/addPeopleToUserTag.spec.ts @@ -0,0 +1,395 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import type { MutationAddPeopleToUserTagArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; +import { + TAG_NOT_FOUND, + USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../../src/constants"; +import { AppUserProfile, TagUser, User } from "../../../src/models"; +import type { TestUserTagType } from "../../helpers/tags"; +import { + createRootTagWithOrg, + createTwoLevelTagsWithOrg, +} from "../../helpers/tags"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; +import { createTestUser } from "../../helpers/userAndOrg"; + +let MONGOOSE_INSTANCE: typeof mongoose; + +let adminUser: TestUserType; +let adminUser2: TestUserType; +let testTag2: TestUserTagType; +let testTag: TestUserTagType; +let testSubTag1: TestUserTagType; +let testOrg2: TestOrganizationType; +let randomUser: TestUserType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [adminUser, , [testTag, testSubTag1]] = await createTwoLevelTagsWithOrg(); + [adminUser2, testOrg2, testTag2] = await createRootTagWithOrg(); + randomUser = await createTestUser(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Mutation -> addPeopleToUserTag", () => { + afterEach(() => { + vi.doUnmock("../../../src/constants"); + vi.resetModules(); + vi.resetAllMocks(); + }); + + it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { + const { requestContext } = await import("../../../src/libraries"); + + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser?._id], + tagId: testTag?._id.toString() ?? "", + }, + }; + + const context = { userId: new Types.ObjectId().toString() }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + await addPeopleToUserTagResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + ); + expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`throws NotFoundError if no tag exists with _id === args.input.tagId `, async () => { + const { requestContext } = await import("../../../src/libraries"); + + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser?._id], + tagId: new Types.ObjectId().toString(), + }, + }; + + const context = { + userId: adminUser?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + await addPeopleToUserTagResolver?.({}, args, context); + } catch (error: unknown) { + expect(spy).toHaveBeenLastCalledWith(TAG_NOT_FOUND.MESSAGE); + expect((error as Error).message).toEqual( + `Translated ${TAG_NOT_FOUND.MESSAGE}`, + ); + } + }); + + it(`throws Not Authorized Error if the current user is not a superadmin or admin of the organization of the tag being assigned`, async () => { + const { requestContext } = await import("../../../src/libraries"); + + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser?._id], + tagId: testTag?._id.toString() ?? "", + }, + }; + + const context = { + userId: randomUser?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + await addPeopleToUserTagResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + ); + expect(spy).toHaveBeenLastCalledWith( + `${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + ); + } + }); + + it(`throws NotFoundError if one of the requested users doesn't exist`, async () => { + const { requestContext } = await import("../../../src/libraries"); + + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser?._id, new Types.ObjectId()], + tagId: testTag?._id.toString() ?? "", + }, + }; + + const context = { userId: adminUser?._id }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + await addPeopleToUserTagResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + ); + expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`throws Error if one of the requested users is not a member of organization of the tag being assigned`, async () => { + const { requestContext } = await import("../../../src/libraries"); + + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser?._id.toString(), randomUser?._id.toString()], + tagId: testTag?._id.toString() ?? "", + }, + }; + + const context = { + userId: adminUser?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + await addPeopleToUserTagResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `Translated ${USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION.MESSAGE}`, + ); + expect(spy).toHaveBeenLastCalledWith( + `${USER_DOES_NOT_BELONG_TO_TAGS_ORGANIZATION.MESSAGE}`, + ); + } + }); + + it(`Tag assignment should be successful and the tag is returned`, async () => { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser2?._id.toString()], + tagId: testTag2?._id.toString() ?? "", + }, + }; + + const context = { + userId: adminUser2?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + const payload = await addPeopleToUserTagResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(testTag2?._id.toString()); + + const tagAssigned = await TagUser.exists({ + tagId: args.input.tagId, + userId: adminUser2?._id, + }); + + expect(tagAssigned).toBeTruthy(); + }); + + it(`Tag assignment should be successful and only new assignments are made and the tag is returned`, async () => { + await User.findOneAndUpdate( + { + _id: randomUser?._id, + }, + { + joinedOrganizations: testOrg2?._id, + }, + ); + + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser2?._id.toString(), randomUser?._id.toString()], + tagId: testTag2?._id.toString() ?? "", + }, + }; + + const context = { + userId: adminUser2?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + const payload = await addPeopleToUserTagResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(testTag2?._id.toString()); + + const tagAssigned = await TagUser.exists({ + tagId: args.input.tagId, + userId: adminUser2?._id, + }); + + expect(tagAssigned).toBeTruthy(); + }); + + it(`Returns the tag if there aren't any new assignments to be made and the tag is returned`, async () => { + await User.findOneAndUpdate( + { + _id: randomUser?._id, + }, + { + joinedOrganizations: testOrg2?._id, + }, + ); + + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser2?._id.toString(), randomUser?._id.toString()], + tagId: testTag2?._id.toString() ?? "", + }, + }; + + const context = { + userId: adminUser2?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + const payload = await addPeopleToUserTagResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(testTag2?._id.toString()); + + const tagAssigned = await TagUser.exists({ + tagId: args.input.tagId, + userId: adminUser2?._id, + }); + + expect(tagAssigned).toBeTruthy(); + }); + + it(`Should assign all the ancestor tags and returns the current tag`, async () => { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [adminUser?._id.toString()], + tagId: testSubTag1?._id.toString() ?? "", + }, + }; + const context = { + userId: adminUser?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + const payload = await addPeopleToUserTagResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(testSubTag1?._id.toString()); + + const subTagAssigned = await TagUser.exists({ + tagId: args.input.tagId, + userId: adminUser?._id, + }); + + const ancestorTagAssigned = await TagUser.exists({ + tagId: testTag?._id.toString() ?? "", + userId: adminUser?._id, + }); + + expect(subTagAssigned).toBeTruthy(); + expect(ancestorTagAssigned).toBeTruthy(); + }); + + it("throws error if user does not have appUserProfile", async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + const args: MutationAddPeopleToUserTagArgs = { + input: { + userIds: [randomUser?._id], + tagId: testTag?._id.toString() ?? "", + }, + }; + + const temp = await createTestUser(); + + await AppUserProfile.deleteOne({ + userId: temp?._id, + }); + + const context = { + userId: temp?._id, + }; + + const { addPeopleToUserTag: addPeopleToUserTagResolver } = await import( + "../../../src/resolvers/Mutation/addPeopleToUserTag" + ); + + await addPeopleToUserTagResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + ); + expect(spy).toHaveBeenLastCalledWith( + `${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + ); + } + }); +}); diff --git a/tests/resolvers/Mutation/addUserToGroupChat.spec.ts b/tests/resolvers/Mutation/addUserToGroupChat.spec.ts deleted file mode 100644 index 8f619536516..00000000000 --- a/tests/resolvers/Mutation/addUserToGroupChat.spec.ts +++ /dev/null @@ -1,274 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { GroupChat, Organization } from "../../../src/models"; -import type { MutationAddUserToGroupChatArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { - afterAll, - afterEach, - beforeAll, - describe, - expect, - it, - vi, -} from "vitest"; -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, - USER_ALREADY_MEMBER_ERROR, - USER_NOT_AUTHORIZED_ADMIN, - USER_NOT_FOUND_ERROR, -} from "../../../src/constants"; -import { cacheOrganizations } from "../../../src/services/OrganizationCache/cacheOrganizations"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChat } from "../../helpers/groupChat"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; - -let testUser: TestUserType; -let testOrganization: TestOrganizationType; -let testGroupChat: TestGroupChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChat(); - testUser = resultArray[0]; - testOrganization = resultArray[1]; - testGroupChat = resultArray[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> addUserToGroupChat", () => { - afterEach(() => { - vi.doUnmock("../../../src/constants"); - vi.resetModules(); - }); - - it(`throws NotFoundError if no groupChat exists with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - const args: MutationAddUserToGroupChatArgs = { - chatId: new Types.ObjectId().toString(), - userId: testUser?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { addUserToGroupChat } = await import( - "../../../src/resolvers/Mutation/addUserToGroupChat" - ); - await addUserToGroupChat?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws NotFoundError if no organization exists with _id === groupChat.organization - for groupChat with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: new Types.ObjectId().toString(), - }, - }, - ); - - const args: MutationAddUserToGroupChatArgs = { - chatId: testGroupChat?.id, - userId: testUser?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { addUserToGroupChat } = await import( - "../../../src/resolvers/Mutation/addUserToGroupChat" - ); - await addUserToGroupChat?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - ORGANIZATION_NOT_FOUND_ERROR.MESSAGE, - ); - } - }); - - it(`throws UnauthorizedError if current user with _id === context.userId is - not an admin of organization with _id === groupChat.organization for groupChat - with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: testOrganization?._id, - }, - }, - ); - - await Organization.updateOne( - { - _id: testOrganization?._id, - }, - { - $set: { - admins: [], - }, - }, - ); - - const args: MutationAddUserToGroupChatArgs = { - chatId: testGroupChat?.id, - userId: testUser?.id, - }; - - const context = { - userId: testUser?.id, - }; - const { addUserToGroupChat } = await import( - "../../../src/resolvers/Mutation/addUserToGroupChat" - ); - await addUserToGroupChat?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ADMIN.MESSAGE}`, - ); - - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ADMIN.MESSAGE); - } - }); - - it(`throws NotFoundError if no user exists with _id === args.userId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - const updatedOrganization = await Organization.findOneAndUpdate( - { - _id: testOrganization?._id, - }, - { - $push: { - admins: testUser?._id, - }, - }, - { - new: true, - }, - ); - - if (updatedOrganization !== null) { - await cacheOrganizations([updatedOrganization]); - } - - const args: MutationAddUserToGroupChatArgs = { - chatId: testGroupChat?.id, - userId: new Types.ObjectId().toString(), - }; - - const context = { - userId: testUser?.id, - }; - - const { addUserToGroupChat } = await import( - "../../../src/resolvers/Mutation/addUserToGroupChat" - ); - await addUserToGroupChat?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws ConflictError if user with _id === args.userId is already a member - of groupChat with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - const args: MutationAddUserToGroupChatArgs = { - chatId: testGroupChat?.id, - userId: testUser?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { addUserToGroupChat } = await import( - "../../../src/resolvers/Mutation/addUserToGroupChat" - ); - await addUserToGroupChat?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_ALREADY_MEMBER_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - USER_ALREADY_MEMBER_ERROR.MESSAGE, - ); - } - }); - - it(`add the groupChat with _id === args.chatId and returns it`, async () => { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - users: [], - }, - }, - ); - - const args: MutationAddUserToGroupChatArgs = { - chatId: testGroupChat?.id, - userId: testUser?.id, - }; - - const context = { - userId: testUser?.id, - }; - const { addUserToGroupChat } = await import( - "../../../src/resolvers/Mutation/addUserToGroupChat" - ); - const addUserToGroupChatPayload = await addUserToGroupChat?.( - {}, - args, - context, - ); - expect(addUserToGroupChatPayload?._id).toEqual(testGroupChat?._id); - expect(addUserToGroupChatPayload?.users).toEqual([testUser?._id]); - }); -}); diff --git a/tests/resolvers/Mutation/adminRemoveGroup.spec.ts b/tests/resolvers/Mutation/adminRemoveGroup.spec.ts deleted file mode 100644 index 584bfd7b493..00000000000 --- a/tests/resolvers/Mutation/adminRemoveGroup.spec.ts +++ /dev/null @@ -1,227 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { GroupChat, Organization, User } from "../../../src/models"; -import type { MutationAdminRemoveGroupArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { nanoid } from "nanoid"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ADMIN, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../../src/constants"; -import { adminRemoveGroup as adminRemoveGroupResolver } from "../../../src/resolvers/Mutation/adminRemoveGroup"; -import { cacheOrganizations } from "../../../src/services/OrganizationCache/cacheOrganizations"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChat } from "../../helpers/groupChat"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; - -let testUser: TestUserType; -let testOrganization: TestOrganizationType; -let testGroupChat: TestGroupChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultsArray = await createTestGroupChat(); - - testUser = resultsArray[0]; - testOrganization = resultsArray[1]; - testGroupChat = resultsArray[2]; - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, - ); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> adminRemoveGroup", () => { - it("throws an error if the user does not have appUserProfile", async () => { - try { - const args: MutationAdminRemoveGroupArgs = { - groupId: testGroupChat?.id, - }; - - const newUser = await User.create({ - email: `email${nanoid().toLowerCase()}@gmail.com`, - password: `pass${nanoid().toLowerCase()}`, - firstName: `firstName${nanoid().toLowerCase()}`, - lastName: `lastName${nanoid().toLowerCase()}`, - image: null, - }); - - const context = { - userId: newUser?.id, - }; - await adminRemoveGroupResolver?.({}, args, context); - } catch (error: unknown) { - // console.log(error);? - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - it(`throws NotFoundError if no groupChat exists with _id === args.groupId`, async () => { - try { - const args: MutationAdminRemoveGroupArgs = { - groupId: new Types.ObjectId().toString(), - }; - - const context = { - userId: testUser?.id, - }; - - await adminRemoveGroupResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws NotFoundError if no organization exists with _id === group.organization for - group with _id === args.groupId`, async () => { - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: new Types.ObjectId().toString(), - }, - }, - ); - - const args: MutationAdminRemoveGroupArgs = { - groupId: testGroupChat?.id, - }; - - const context = { - userId: testUser?.id, - }; - - await adminRemoveGroupResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - ORGANIZATION_NOT_FOUND_ERROR.MESSAGE, - ); - } - }); - - it(`throws NotFoundError if no user exists with _id === context.userId`, async () => { - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: testOrganization?._id, - }, - }, - ); - - const args: MutationAdminRemoveGroupArgs = { - groupId: testGroupChat?.id, - }; - - const context = { - userId: new Types.ObjectId().toString(), - }; - - await adminRemoveGroupResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws UnauthorizedError if for user with _id === context.userId is not an - admin of orgnanization with _id === args.organizationId`, async () => { - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: testOrganization?._id, - }, - }, - ); - - const updatedOrganization = await Organization.findOneAndUpdate( - { - _id: testOrganization?._id, - }, - { - $set: { - admins: [], - }, - }, - { - new: true, - }, - ); - if (updatedOrganization) cacheOrganizations([updatedOrganization]); - - const args: MutationAdminRemoveGroupArgs = { - groupId: testGroupChat?.id, - }; - - const context = { - userId: testUser?.id, - }; - - await adminRemoveGroupResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ADMIN.MESSAGE, - ); - } - }); - - it(`deletes the post and returns it`, async () => { - const updatedOrganization = await Organization.findOneAndUpdate( - { - _id: testOrganization?._id, - }, - { - $push: { - admins: testUser?._id, - }, - }, - { - new: true, - }, - ); - if (updatedOrganization) cacheOrganizations([updatedOrganization]); - - const args: MutationAdminRemoveGroupArgs = { - groupId: testGroupChat?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const adminRemoveGroupPayload = await adminRemoveGroupResolver?.( - {}, - args, - context, - ); - - expect(adminRemoveGroupPayload).toEqual({ - ...testGroupChat?.toObject(), - updatedAt: expect.anything(), - }); - }); -}); diff --git a/tests/resolvers/Mutation/assignToUserTags.spec.ts b/tests/resolvers/Mutation/assignToUserTags.spec.ts new file mode 100644 index 00000000000..4d9397d9fb6 --- /dev/null +++ b/tests/resolvers/Mutation/assignToUserTags.spec.ts @@ -0,0 +1,322 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import type { MutationAssignToUserTagsArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; +import { + TAG_NOT_FOUND, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../../src/constants"; +import { + AppUserProfile, + OrganizationTagUser, + TagUser, + User, +} from "../../../src/models"; +import type { TestUserTagType } from "../../helpers/tags"; +import { + createRootTagsWithOrg, + createTwoLevelTagsWithOrg, +} from "../../helpers/tags"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; +import { createTestUser } from "../../helpers/userAndOrg"; + +let MONGOOSE_INSTANCE: typeof mongoose; + +let adminUser: TestUserType; +let adminUser2: TestUserType; +let testTag2: TestUserTagType; +let testTag3: TestUserTagType; +let testTag: TestUserTagType; +let testSubTag1: TestUserTagType; +let testOrg1: TestOrganizationType; +let testOrg2: TestOrganizationType; +let randomUser1: TestUserType; +let randomUser2: TestUserType; +let randomUser3: TestUserType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [adminUser, testOrg1, [testTag, testSubTag1]] = + await createTwoLevelTagsWithOrg(); + [adminUser2, testOrg2, [testTag2, testTag3]] = await createRootTagsWithOrg(2); + randomUser1 = await createTestUser(); + randomUser2 = await createTestUser(); + randomUser3 = await createTestUser(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Mutation -> assignToUserTags", () => { + afterEach(() => { + vi.doUnmock("../../../src/constants"); + vi.resetModules(); + vi.resetAllMocks(); + }); + + it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: new Types.ObjectId().toString() }, + expectedError: USER_NOT_FOUND_ERROR.MESSAGE, + }); + }); + + it(`throws NotFoundError if no tag exists with _id === args.input.currentTagId `, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: new Types.ObjectId().toString(), + }, + }, + context: { userId: adminUser?._id }, + expectedError: TAG_NOT_FOUND.MESSAGE, + }); + }); + + it(`throws Not Authorized Error if the current user is not a superadmin or admin of the organization `, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: randomUser1?._id }, + expectedError: USER_NOT_AUTHORIZED_ERROR.MESSAGE, + }); + }); + + it(`throws NotFoundError if one of the selected tags doesn't exist`, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [ + testTag?._id.toString() ?? "", + new Types.ObjectId().toString(), + ], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: adminUser?._id }, + expectedError: TAG_NOT_FOUND.MESSAGE, + }); + }); + + it("throws error if user does not have appUserProfile", async () => { + const temp = await createTestUser(); + await AppUserProfile.deleteOne({ + userId: temp?._id, + }); + + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: temp?._id }, + expectedError: USER_NOT_AUTHORIZED_ERROR.MESSAGE, + }); + }); + + it(`Tag assignment should be successful and the tag is returned`, async () => { + // assign testTag2 to random users + await Promise.all([ + User.findOneAndUpdate( + { + _id: randomUser2?._id, + }, + { + joinedOrganizations: testOrg2, + }, + ), + User.findOneAndUpdate( + { + _id: randomUser3?._id, + }, + { + joinedOrganizations: testOrg2, + }, + ), + TagUser.create({ + userId: randomUser2?._id, + tagId: testTag2?._id, + organizationId: testTag2?.organizationId, + }), + TagUser.create({ + userId: randomUser3?._id, + tagId: testTag2?._id, + organizationId: testTag2?.organizationId, + }), + ]); + + // now assign them to a new tag with the help of the mutation + const args: MutationAssignToUserTagsArgs = { + input: { + selectedTagIds: [testTag3?._id.toString() ?? ""], + currentTagId: testTag2?._id.toString() ?? "", + }, + }; + + const context = { + userId: adminUser2?._id, + }; + + const { assignToUserTags: assignToUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/assignToUserTags" + ); + + const payload = await assignToUserTagsResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(testTag2?._id.toString()); + + const tagAssignedToRandomUser2 = await TagUser.exists({ + tagId: testTag3, + userId: randomUser2?._id, + }); + + const tagAssignedToRandomUser3 = await TagUser.exists({ + tagId: testTag3, + userId: randomUser3?._id, + }); + + expect(tagAssignedToRandomUser2).toBeTruthy(); + expect(tagAssignedToRandomUser3).toBeTruthy(); + }); + + it(`Should assign all the ancestor tags and returns the current tag`, async () => { + // create a new tag with the organization + const newTestTag = await OrganizationTagUser.create({ + name: "newTestTag", + organizationId: testOrg1?._id, + }); + + // assign this new test tag to random users + await Promise.all([ + User.findOneAndUpdate( + { + _id: randomUser2?._id, + }, + { + joinedOrganizations: testOrg1, + }, + ), + User.findOneAndUpdate( + { + _id: randomUser3?._id, + }, + { + joinedOrganizations: testOrg1, + }, + ), + TagUser.create({ + userId: randomUser2?._id, + tagId: newTestTag?._id, + organizationId: newTestTag?.organizationId, + }), + TagUser.create({ + userId: randomUser3?._id, + tagId: newTestTag?._id, + organizationId: newTestTag?.organizationId, + }), + ]); + + // now assign them a new sub tag, which will automatically assign them the parent tag also + const args: MutationAssignToUserTagsArgs = { + input: { + selectedTagIds: [testSubTag1?._id.toString() ?? ""], + currentTagId: newTestTag?._id.toString() ?? "", + }, + }; + const context = { + userId: adminUser?._id, + }; + + const { assignToUserTags: assignToUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/assignToUserTags" + ); + + const payload = await assignToUserTagsResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(newTestTag?._id.toString()); + + const subTagAssignedToRandomUser2 = await TagUser.exists({ + tagId: testSubTag1?._id, + userId: randomUser2?._id, + }); + + const subTagAssignedToRandomUser3 = await TagUser.exists({ + tagId: testSubTag1?._id, + userId: randomUser3?._id, + }); + + expect(subTagAssignedToRandomUser2).toBeTruthy(); + expect(subTagAssignedToRandomUser3).toBeTruthy(); + + const ancestorTagAssignedToRandomUser2 = await TagUser.exists({ + tagId: testTag?._id.toString() ?? "", + userId: randomUser2?._id, + }); + + const ancestorTagAssignedToRandomUser3 = await TagUser.exists({ + tagId: testTag?._id.toString() ?? "", + userId: randomUser3?._id, + }); + + expect(ancestorTagAssignedToRandomUser2).toBeTruthy(); + expect(ancestorTagAssignedToRandomUser3).toBeTruthy(); + }); +}); + +const testErrorScenario = async ({ + args, + context, + expectedError, +}: { + args: MutationAssignToUserTagsArgs; + context: { userId: string }; + expectedError: string; +}): Promise => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + try { + const { assignToUserTags: assignToUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/assignToUserTags" + ); + await assignToUserTagsResolver?.({}, args, context); + throw new Error("Expected error was not thrown"); + } catch (error: unknown) { + if (error instanceof Error) { + expect(error.message).toEqual(`Translated ${expectedError}`); + } else { + throw new Error("Unexpected error type"); + } + expect(spy).toHaveBeenLastCalledWith(expectedError); + } +}; diff --git a/tests/resolvers/Mutation/createActionItem.spec.ts b/tests/resolvers/Mutation/createActionItem.spec.ts index e1093a49755..c8b35b5285a 100644 --- a/tests/resolvers/Mutation/createActionItem.spec.ts +++ b/tests/resolvers/Mutation/createActionItem.spec.ts @@ -1,85 +1,57 @@ -import "dotenv/config"; import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { connect, disconnect } from "../../helpers/db"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { + TestEventType, + TestEventVolunteerGroupType, + TestEventVolunteerType, +} from "../../helpers/events"; +import type { TestUserType } from "../../helpers/user"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import type { InterfaceActionItem } from "../../../src/models"; +import { ActionItemCategory } from "../../../src/models"; import { ACTION_ITEM_CATEGORY_IS_DISABLED, ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR, EVENT_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, - USER_NOT_MEMBER_FOR_ORGANIZATION, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_NOT_FOUND_ERROR, } from "../../../src/constants"; -import { createActionItem as createActionItemResolver } from "../../../src/resolvers/Mutation/createActionItem"; -import type { MutationCreateActionItemArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; -import { createTestUser } from "../../helpers/userAndOrg"; - -import { nanoid } from "nanoid"; -import { - ActionItemCategory, - AppUserProfile, - Event, - User, -} from "../../../src/models"; -import type { TestActionItemCategoryType } from "../../helpers/actionItemCategory"; -import { createTestCategory } from "../../helpers/actionItemCategory"; -import type { TestEventType } from "../../helpers/events"; +import { requestContext } from "../../../src/libraries"; +import { createActionItem } from "../../../src/resolvers/Mutation/createActionItem"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; +import type { TestActionItemType } from "../../helpers/actionItem"; -let randomUser: TestUserType; -let randomUser2: TestUserType; -// let superAdminTestUserAppProfile: TestAppUserProfileType; -let testUser: TestUserType; +let MONGOOSE_INSTANCE: typeof mongoose; let testOrganization: TestOrganizationType; -let testCategory: TestActionItemCategoryType; -let testDisabledCategory: TestActionItemCategoryType; let testEvent: TestEventType; -let MONGOOSE_INSTANCE: typeof mongoose; +let testUser1: TestUserType; +let testActionItem1: TestActionItemType; +let testEventVolunteer1: TestEventVolunteerType; +let testEventVolunteerGroup: TestEventVolunteerGroupType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( (message) => message, ); - - randomUser = await createTestUser(); - randomUser2 = await createTestUser(); - - await AppUserProfile.updateOne( - { - userId: randomUser2?._id, - }, - { - isSuperAdmin: true, - }, - ); - - [testUser, testOrganization, testCategory] = await createTestCategory(); - - testDisabledCategory = await ActionItemCategory.create({ - name: "a disabled category", - organizationId: testOrganization?._id, - isDisabled: true, - creatorId: testUser?._id, - }); - - testEvent = await Event.create({ - title: `title${nanoid().toLowerCase()}`, - description: `description${nanoid().toLowerCase()}`, - allDay: true, - startDate: new Date(), - recurring: false, - isPublic: true, - isRegisterable: true, - creatorId: randomUser?._id, - admins: [randomUser?._id], - organization: testOrganization?._id, - }); + const [ + organization, + event, + user1, + , + volunteer1, + , + volunteerGroup, + actionItem1, + ] = await createVolunteerAndActions(); + + testOrganization = organization; + testEvent = event; + testUser1 = user1; + testEventVolunteer1 = volunteer1; + testEventVolunteerGroup = volunteerGroup; + testActionItem1 = actionItem1; }); afterAll(async () => { @@ -87,255 +59,161 @@ afterAll(async () => { }); describe("resolvers -> Mutation -> createActionItem", () => { - it(`throws NotFoundError if no user exists with _id === context.userId`, async () => { - try { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, - }, - actionItemCategoryId: testCategory?._id, - }; - - const context = { - userId: new Types.ObjectId().toString(), - }; - - await createActionItemResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws NotFoundError if no actionItemCategory exists with _id === args.actionItemCategoryId`, async () => { + it(`throws EventVolunteer Not Found`, async () => { try { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, + (await createActionItem?.( + {}, + { + data: { + assigneeId: testEvent?._id, + assigneeType: "EventVolunteer", + }, + actionItemCategoryId: testEventVolunteer1?._id, }, - actionItemCategoryId: new Types.ObjectId().toString(), - }; - - const context = { - userId: testUser?._id, - }; - - await createActionItemResolver?.({}, args, context); + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; } catch (error: unknown) { expect((error as Error).message).toEqual( - ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR.MESSAGE, + EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE, ); } }); - it(`throws ConflictError if the actionItemCategory is disabled`, async () => { + it(`throws EventVolunteerGroup Not Found`, async () => { try { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, + (await createActionItem?.( + {}, + { + data: { + assigneeId: testEvent?._id, + assigneeType: "EventVolunteerGroup", + }, + actionItemCategoryId: testEventVolunteer1?._id, }, - actionItemCategoryId: testDisabledCategory._id, - }; - - const context = { - userId: testUser?._id, - }; - - await createActionItemResolver?.({}, args, context); + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; } catch (error: unknown) { expect((error as Error).message).toEqual( - ACTION_ITEM_CATEGORY_IS_DISABLED.MESSAGE, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE, ); } }); - it(`throws NotFoundError if no user exists with _id === args.data.assigneeId`, async () => { + it(`throws ActionItemCategory Not Found`, async () => { try { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: new Types.ObjectId().toString(), + (await createActionItem?.( + {}, + { + data: { + assigneeId: testEventVolunteer1?._id, + assigneeType: "EventVolunteer", + }, + actionItemCategoryId: testEventVolunteer1?._id, }, - actionItemCategoryId: testCategory?._id, - }; - - const context = { - userId: testUser?._id, - }; - - await createActionItemResolver?.({}, args, context); + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; } catch (error: unknown) { - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + expect((error as Error).message).toEqual( + ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR.MESSAGE, + ); } }); - it(`throws NotFoundError new assignee is not a member of the organization`, async () => { + it(`throws ActionItemCategory is Disabled`, async () => { + const disabledCategory = await ActionItemCategory.create({ + creatorId: testUser1?._id, + organizationId: testOrganization?._id, + name: "Disabled Category", + isDisabled: true, + }); try { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, + (await createActionItem?.( + {}, + { + data: { + assigneeId: testEventVolunteer1?._id, + assigneeType: "EventVolunteer", + }, + actionItemCategoryId: disabledCategory?._id.toString(), }, - actionItemCategoryId: testCategory?._id, - }; - - const context = { - userId: testUser?._id, - }; - - await createActionItemResolver?.({}, args, context); + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; } catch (error: unknown) { expect((error as Error).message).toEqual( - USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE, + ACTION_ITEM_CATEGORY_IS_DISABLED.MESSAGE, ); } }); - it(`throws NotFoundError if no event exists with _id === args.data.eventId`, async () => { - await User.findOneAndUpdate( - { - _id: randomUser?._id, - }, - { - $push: { joinedOrganizations: testOrganization?._id }, - }, - ); - + it(`throws Event Not Found`, async () => { try { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, - eventId: new Types.ObjectId().toString(), + (await createActionItem?.( + {}, + { + data: { + assigneeId: testEventVolunteer1?._id, + assigneeType: "EventVolunteer", + eventId: testUser1?._id.toString(), + }, + actionItemCategoryId: testActionItem1.actionItemCategory.toString(), }, - actionItemCategoryId: testCategory?._id, - }; - - const context = { - userId: randomUser?._id, - }; - - await createActionItemResolver?.({}, args, context); + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; } catch (error: unknown) { expect((error as Error).message).toEqual(EVENT_NOT_FOUND_ERROR.MESSAGE); } }); - it(`throws NotAuthorizedError if the user is not authorized for performing the operation`, async () => { - try { - const args: MutationCreateActionItemArgs = { + it(`Create Action Item (EventVolunteer) `, async () => { + const createdItem = (await createActionItem?.( + {}, + { data: { - assigneeId: randomUser?._id, + assigneeId: testEventVolunteer1?._id, + assigneeType: "EventVolunteer", + eventId: testEvent?._id.toString(), }, - actionItemCategoryId: testCategory?._id, - }; - - const context = { - userId: randomUser?._id, - }; - - await createActionItemResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - - it(`creates the actionItem when user is authorized as an eventAdmin`, async () => { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, - eventId: testEvent?._id.toString() ?? "", + actionItemCategoryId: testActionItem1.actionItemCategory.toString(), }, - actionItemCategoryId: testCategory?._id, - }; - - const context = { - userId: randomUser?._id, - }; - - const createActionItemPayload = await createActionItemResolver?.( - {}, - args, - context, - ); + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; - expect(createActionItemPayload).toEqual( - expect.objectContaining({ - actionItemCategory: testCategory?._id, - }), - ); + expect(createdItem).toBeDefined(); + expect(createdItem.creator).toEqual(testUser1?._id); }); - it(`creates the actionItem when user is authorized as an orgAdmin`, async () => { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, - }, - actionItemCategoryId: testCategory?._id, - }; - - const context = { - userId: testUser?._id, - }; - - const createActionItemPayload = await createActionItemResolver?.( + it(`Create Action Item (EventVolunteerGroup) `, async () => { + const createdItem = (await createActionItem?.( {}, - args, - context, - ); - - expect(createActionItemPayload).toEqual( - expect.objectContaining({ - actionItemCategory: testCategory?._id, - }), - ); - }); - - it(`creates the actionItem when user is authorized as superadmin`, async () => { - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, + { + data: { + assigneeId: testEventVolunteerGroup?._id, + assigneeType: "EventVolunteerGroup", + eventId: testEvent?._id.toString(), + }, + actionItemCategoryId: testActionItem1.actionItemCategory.toString(), }, - actionItemCategoryId: testCategory?._id, - }; + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; - const context = { - userId: randomUser2?._id, - }; - // const superAdmin = await AppUserProfile.findOne({ - // userId: randomUser2?._id, - // }); - // console.log(superAdmin) + expect(createdItem).toBeDefined(); + expect(createdItem.creator).toEqual(testUser1?._id); + }); - const createActionItemPayload = await createActionItemResolver?.( + it(`Create Action Item (User) `, async () => { + const createdItem = (await createActionItem?.( {}, - args, - context, - ); - - expect(createActionItemPayload).toEqual( - expect.objectContaining({ - actionItemCategory: testCategory?._id, - }), - ); - }); - it("throws error if the user does not have appUserProfile", async () => { - await AppUserProfile.deleteOne({ - userId: randomUser?._id, - }); - const args: MutationCreateActionItemArgs = { - data: { - assigneeId: randomUser?._id, + { + data: { + assigneeId: testUser1?._id.toString() as string, + assigneeType: "User", + }, + actionItemCategoryId: testActionItem1.actionItemCategory.toString(), }, - actionItemCategoryId: testCategory?._id, - }; - const context = { - userId: randomUser?._id, - }; - try { - await createActionItemResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceActionItem; + + expect(createdItem).toBeDefined(); + expect(createdItem.creator).toEqual(testUser1?._id); }); }); diff --git a/tests/resolvers/Mutation/createDirectChat.spec.ts b/tests/resolvers/Mutation/createChat.spec.ts similarity index 61% rename from tests/resolvers/Mutation/createDirectChat.spec.ts rename to tests/resolvers/Mutation/createChat.spec.ts index 7c46e940920..560c9b62074 100644 --- a/tests/resolvers/Mutation/createDirectChat.spec.ts +++ b/tests/resolvers/Mutation/createChat.spec.ts @@ -1,7 +1,7 @@ import "dotenv/config"; import type mongoose from "mongoose"; import { Types } from "mongoose"; -import type { MutationCreateDirectChatArgs } from "../../../src/types/generatedGraphQLTypes"; +import type { MutationCreateChatArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; import { @@ -29,7 +29,6 @@ let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); const resultsArray = await createTestUserAndOrganization(); - testUser = resultsArray[0]; testOrganization = resultsArray[1]; }); @@ -38,7 +37,7 @@ afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> Mutation -> createDirectChat", () => { +describe("resolvers -> Mutation -> createChat", () => { afterEach(() => { vi.doUnmock("../../../src/constants"); vi.resetModules(); @@ -50,20 +49,21 @@ describe("resolvers -> Mutation -> createDirectChat", () => { .spyOn(requestContext, "translate") .mockImplementationOnce((message) => message); try { - const args: MutationCreateDirectChatArgs = { + const args: MutationCreateChatArgs = { data: { organizationId: new Types.ObjectId().toString(), userIds: [], + isGroup: true, }, }; const context = { userId: testUser?.id, }; - const { createDirectChat: createDirectChatResolver } = await import( - "../../../src/resolvers/Mutation/createDirectChat" + const { createChat: createChatResolver } = await import( + "../../../src/resolvers/Mutation/createChat" ); - await createDirectChatResolver?.({}, args, context); + await createChatResolver?.({}, args, context); } catch (error: unknown) { expect(spy).toBeCalledWith(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual( @@ -78,10 +78,11 @@ describe("resolvers -> Mutation -> createDirectChat", () => { .spyOn(requestContext, "translate") .mockImplementationOnce((message) => message); try { - const args: MutationCreateDirectChatArgs = { + const args: MutationCreateChatArgs = { data: { - organizationId: testOrganization?.id, + organizationId: testOrganization?._id, userIds: [new Types.ObjectId().toString()], + isGroup: true, }, }; @@ -89,37 +90,60 @@ describe("resolvers -> Mutation -> createDirectChat", () => { userId: testUser?.id, }; - const { createDirectChat: createDirectChatResolver } = await import( - "../../../src/resolvers/Mutation/createDirectChat" + const { createChat: createChatResolver } = await import( + "../../../src/resolvers/Mutation/createChat" ); - await createDirectChatResolver?.({}, args, context); + await createChatResolver?.({}, args, context); } catch (error: unknown) { expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); } }); - it(`creates the directChat and returns it`, async () => { - const args: MutationCreateDirectChatArgs = { + it(`creates the chat and returns it`, async () => { + const args: MutationCreateChatArgs = { data: { organizationId: testOrganization?.id, userIds: [testUser?.id], + isGroup: false, }, }; const context = { userId: testUser?.id, }; - const { createDirectChat: createDirectChatResolver } = await import( - "../../../src/resolvers/Mutation/createDirectChat" + const { createChat: createChatResolver } = await import( + "../../../src/resolvers/Mutation/createChat" ); - const createDirectChatPayload = await createDirectChatResolver?.( - {}, - args, - context, + const createChatPayload = await createChatResolver?.({}, args, context); + + expect(createChatPayload).toEqual( + expect.objectContaining({ + creatorId: testUser?._id, + users: [testUser?._id], + }), ); + }); + + it(`creates the chat and returns it`, async () => { + const args: MutationCreateChatArgs = { + data: { + organizationId: testOrganization?.id, + userIds: [testUser?.id], + isGroup: false, + }, + }; + + const context = { + userId: testUser?.id, + }; + const { createChat: createChatResolver } = await import( + "../../../src/resolvers/Mutation/createChat" + ); + + const createChatPayload = await createChatResolver?.({}, args, context); - expect(createDirectChatPayload).toEqual( + expect(createChatPayload).toEqual( expect.objectContaining({ creatorId: testUser?._id, users: [testUser?._id], diff --git a/tests/resolvers/Mutation/createEventVolunteer.spec.ts b/tests/resolvers/Mutation/createEventVolunteer.spec.ts index 8d339b4370a..7734361eefe 100644 --- a/tests/resolvers/Mutation/createEventVolunteer.spec.ts +++ b/tests/resolvers/Mutation/createEventVolunteer.spec.ts @@ -45,10 +45,11 @@ beforeAll(async () => { [eventAdminUser, , testEvent] = await createTestEvent(); testGroup = await EventVolunteerGroup.create({ - creatorId: eventAdminUser?._id, - eventId: testEvent?._id, - leaderId: eventAdminUser?._id, + creator: eventAdminUser?._id, + event: testEvent?._id, + leader: eventAdminUser?._id, name: "Test group", + volunteers: [eventAdminUser?._id, testUser2?._id, testUser1?._id], }); }); @@ -230,22 +231,15 @@ describe("resolvers -> Mutation -> createEventVolunteer", () => { context, ); - const updatedGroup = await EventVolunteerGroup.findOne({ - _id: testGroup?._id, - }); - - expect(updatedGroup?.volunteers.toString()).toEqual( - [createdVolunteer?._id.toString()].toString(), - ); - expect(createdVolunteer).toEqual( expect.objectContaining({ - eventId: new Types.ObjectId(testEvent?.id), - userId: testUser2?._id, - groupId: testGroup?._id, - creatorId: eventAdminUser?._id, - isInvited: true, - isAssigned: false, + event: new Types.ObjectId(testEvent?.id), + user: testUser2?._id, + groups: [], + creator: eventAdminUser?._id, + hasAccepted: false, + isPublic: true, + hoursVolunteered: 0, }), ); }); diff --git a/tests/resolvers/Mutation/createEventVolunteerGroup.spec.ts b/tests/resolvers/Mutation/createEventVolunteerGroup.spec.ts index f41663b29b8..5604676eacc 100644 --- a/tests/resolvers/Mutation/createEventVolunteerGroup.spec.ts +++ b/tests/resolvers/Mutation/createEventVolunteerGroup.spec.ts @@ -54,6 +54,8 @@ describe("resolvers -> Mutation -> createEventVolunteerGroup", () => { const args: MutationCreateEventVolunteerGroupArgs = { data: { name: "Test group", + leaderId: testUser?._id, + volunteerUserIds: [testUser?._id], eventId: testEvent?._id, }, }; @@ -82,6 +84,8 @@ describe("resolvers -> Mutation -> createEventVolunteerGroup", () => { const args: MutationCreateEventVolunteerGroupArgs = { data: { name: "Test group", + leaderId: testUser?._id, + volunteerUserIds: [testUser?._id], eventId: new Types.ObjectId().toString(), }, }; @@ -111,6 +115,8 @@ describe("resolvers -> Mutation -> createEventVolunteerGroup", () => { const args: MutationCreateEventVolunteerGroupArgs = { data: { name: "Test group", + leaderId: testUser?._id, + volunteerUserIds: [testUser?._id], eventId: testEvent?._id, }, }; @@ -137,6 +143,8 @@ describe("resolvers -> Mutation -> createEventVolunteerGroup", () => { const args: MutationCreateEventVolunteerGroupArgs = { data: { name: "Test group", + leaderId: eventAdminUser?._id, + volunteerUserIds: [testUser?._id], eventId: testEvent?._id, }, }; @@ -163,9 +171,9 @@ describe("resolvers -> Mutation -> createEventVolunteerGroup", () => { expect(createdGroup).toEqual( expect.objectContaining({ name: "Test group", - eventId: new Types.ObjectId(testEvent?.id), - creatorId: eventAdminUser?._id, - leaderId: eventAdminUser?._id, + event: new Types.ObjectId(testEvent?.id), + creator: eventAdminUser?._id, + leader: eventAdminUser?._id, }), ); }); diff --git a/tests/resolvers/Mutation/createGroupChat.spec.ts b/tests/resolvers/Mutation/createGroupChat.spec.ts deleted file mode 100644 index 7fc0400b7a1..00000000000 --- a/tests/resolvers/Mutation/createGroupChat.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import type { MutationCreateGroupChatArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { createGroupChat as createGroupChatResolver } from "../../../src/resolvers/Mutation/createGroupChat"; -import { - ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../../src/constants"; -import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; -import { createTestUserAndOrganization } from "../../helpers/userAndOrg"; - -let testUser: TestUserType; -let MONGOOSE_INSTANCE: typeof mongoose; -let testOrganization: TestOrganizationType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultsArray = await createTestUserAndOrganization(); - - testUser = resultsArray[0]; - testOrganization = resultsArray[1]; - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, - ); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> createGroupChat", () => { - it(`throws NotFoundError if no organization exists with _id === args.data.organizationId`, async () => { - try { - const args: MutationCreateGroupChatArgs = { - data: { - organizationId: new Types.ObjectId().toString(), - title: "", - userIds: [], - }, - }; - - const context = { - userId: testUser?.id, - }; - - await createGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - ORGANIZATION_NOT_FOUND_ERROR.MESSAGE, - ); - } - }); - - it(`throws NotFoundError if no user exists for any one of the ids in args.data.userIds`, async () => { - try { - const args: MutationCreateGroupChatArgs = { - data: { - organizationId: testOrganization?.id, - title: "", - userIds: [new Types.ObjectId().toString()], - }, - }; - - const context = { - userId: testUser?.id, - }; - - await createGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`creates the groupChat and returns it`, async () => { - const args: MutationCreateGroupChatArgs = { - data: { - organizationId: testOrganization?.id, - title: "title", - userIds: [testUser?.id], - }, - }; - - const context = { - userId: testUser?.id, - }; - - const createGroupChatPayload = await createGroupChatResolver?.( - {}, - args, - context, - ); - - expect(createGroupChatPayload).toEqual( - expect.objectContaining({ - title: "title", - creatorId: testUser?._id, - users: [testUser?._id], - organization: testOrganization?._id, - }), - ); - }); -}); diff --git a/tests/resolvers/Mutation/createMessageChat.spec.ts b/tests/resolvers/Mutation/createMessageChat.spec.ts deleted file mode 100644 index d77a2b1d544..00000000000 --- a/tests/resolvers/Mutation/createMessageChat.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import type { InterfaceMessageChat } from "../../../src/models"; -import { AppUserProfile, User } from "../../../src/models"; -import type { MutationCreateMessageChatArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { nanoid } from "nanoid"; -import { - afterAll, - afterEach, - beforeAll, - describe, - expect, - it, - vi, -} from "vitest"; -import { - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../../src/constants"; -import type { TestUserType } from "../../helpers/userAndOrg"; - -let testUsers: TestUserType[]; -// let testAppUserProfile: TestAppUserProfileType[]; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - - testUsers = await User.insertMany([ - { - email: `email${nanoid().toLowerCase()}@gmail.com`, - password: "password", - firstName: "firstName", - lastName: "lastName", - }, - { - email: `email${nanoid().toLowerCase()}@gmail.com`, - password: "password", - firstName: "firstName", - lastName: "lastName", - }, - ]); - const appUserProfiles = testUsers.map((user) => ({ - userId: user?._id, - })); - await AppUserProfile.insertMany(appUserProfiles); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> createMessageChat", () => { - afterEach(() => { - vi.doUnmock("../../../src/constants"); - vi.resetModules(); - }); - - it(`throws NotFoundError if no user exists with _id === args.data.receiver`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const args: MutationCreateMessageChatArgs = { - data: { - message: "", - receiver: new Types.ObjectId().toString(), - }, - }; - - const context = { - userId: testUsers[0]?.id, - }; - - const { createMessageChat: createMessageChatResolver } = await import( - "../../../src/resolvers/Mutation/createMessageChat" - ); - await createMessageChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - it(`throws user not found error if no user exist with id==context.userId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const args: MutationCreateMessageChatArgs = { - data: { - message: "", - receiver: testUsers[1]?.id, - }, - }; - - const context = { - userId: new Types.ObjectId().toString(), - }; - - const { createMessageChat: createMessageChatResolver } = await import( - "../../../src/resolvers/Mutation/createMessageChat" - ); - await createMessageChatResolver?.({}, args, context); - } catch (error: unknown) { - // console.log(error); - expect(spy).toHaveBeenCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - it("throws error if receiver user does not have appProfile", async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const newUser = await User.create({ - email: `email${nanoid().toLowerCase()}@gmail.com`, - password: `pass${nanoid().toLowerCase()}`, - firstName: `firstName${nanoid().toLowerCase()}`, - lastName: `lastName${nanoid().toLowerCase()}`, - image: null, - }); - const args: MutationCreateMessageChatArgs = { - data: { - message: "", - receiver: newUser.id, - }, - }; - - const context = { - userId: testUsers[0]?.id, - }; - - const { createMessageChat: createMessageChatResolver } = await import( - "../../../src/resolvers/Mutation/createMessageChat" - ); - await createMessageChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, - ); - } - }); - it("throws error if sender user does not have appProfile", async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const newUser = await User.create({ - email: `email${nanoid().toLowerCase()}@gmail.com`, - password: `pass${nanoid().toLowerCase()}`, - firstName: `firstName${nanoid().toLowerCase()}`, - lastName: `lastName${nanoid().toLowerCase()}`, - image: null, - }); - const args: MutationCreateMessageChatArgs = { - data: { - message: "", - receiver: testUsers[1]?.id, - }, - }; - - const context = { - userId: newUser.id, - }; - - const { createMessageChat: createMessageChatResolver } = await import( - "../../../src/resolvers/Mutation/createMessageChat" - ); - await createMessageChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, - ); - } - }); - - it(`creates the organization and returns it`, async () => { - const args: MutationCreateMessageChatArgs = { - data: { - message: "message", - receiver: testUsers[1]?.id, - }, - }; - - const pubsub = { - publish: ( - _action: "CHAT_CHANNEL", - _payload: { - directMessageChat: InterfaceMessageChat; - }, - ): { - _action: string; - _payload: { directMessageChat: InterfaceMessageChat }; - } => { - return { _action, _payload }; - }, - }; - - const context = { - userId: testUsers[0]?.id, - pubsub, - }; - - const { createMessageChat: createMessageChatResolver } = await import( - "../../../src/resolvers/Mutation/createMessageChat" - ); - const createMessageChatPayload = await createMessageChatResolver?.( - {}, - args, - context, - ); - - expect(createMessageChatPayload).toEqual( - expect.objectContaining({ - sender: testUsers[0]?._id, - receiver: testUsers[1]?._id, - message: "message", - languageBarrier: false, - }), - ); - }); -}); diff --git a/tests/resolvers/Mutation/createPost.spec.ts b/tests/resolvers/Mutation/createPost.spec.ts index 6d7e154e6e6..8a03f389e6d 100644 --- a/tests/resolvers/Mutation/createPost.spec.ts +++ b/tests/resolvers/Mutation/createPost.spec.ts @@ -1,7 +1,7 @@ import "dotenv/config"; import type mongoose from "mongoose"; import { Types } from "mongoose"; -import type { MutationCreatePostArgs } from "../../../src/types/generatedGraphQLTypes"; +import type { Response } from "express"; import { connect, disconnect } from "../../helpers/db"; import { @@ -14,18 +14,17 @@ import { vi, } from "vitest"; import { - BASE_URL, + INTERNAL_SERVER_ERROR, + INVALID_FILE_TYPE, LENGTH_VALIDATION_ERROR, ORGANIZATION_NOT_FOUND_ERROR, + PLEASE_PROVIDE_TITLE, USER_NOT_AUTHORIZED_ERROR, USER_NOT_AUTHORIZED_TO_PIN, USER_NOT_FOUND_ERROR, USER_NOT_MEMBER_FOR_ORGANIZATION, } from "../../../src/constants"; import { AppUserProfile, Organization, Post } from "../../../src/models"; -import { createPost as createPostResolverImage } from "../../../src/resolvers/Mutation/createPost"; -import * as uploadEncodedImage from "../../../src/utilities/encodedImageStorage/uploadEncodedImage"; -import * as uploadEncodedVideo from "../../../src/utilities/encodedVideoStorage/uploadEncodedVideo"; import type { TestOrganizationType, TestUserType, @@ -34,14 +33,31 @@ import { createTestUser, createTestUserAndOrganization, } from "../../helpers/userAndOrg"; +import type { InterfaceAuthenticatedRequest } from "../../../src/middleware"; +import { createPost } from "../../../src/REST/controllers/mutation"; +import * as uploadFileService from "../../../src/REST/services/file"; +import type { InterfaceUploadedFileResponse } from "../../../src/REST/services/file/uploadFile"; + +vi.mock("../../../src/libraries/requestContext", () => ({ + translate: (message: string): string => `Translated ${message}`, +})); let testUser: TestUserType; let testOrganization: TestOrganizationType; let MONGOOSE_INSTANCE: typeof mongoose; -vi.mock("../../utilities/uploadEncodedImage", () => ({ - uploadEncodedImage: vi.fn(), -})); +interface InterfaceMockResponse extends Omit { + status(code: number): InterfaceMockResponse; + json(data: unknown): InterfaceMockResponse; +} + +// Mock response object with proper return type +const mockResponse = (): InterfaceMockResponse => { + const res = {} as InterfaceMockResponse; + res.status = vi.fn().mockReturnValue(res); + res.json = vi.fn().mockReturnValue(res); + return res; +}; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); @@ -54,559 +70,346 @@ afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> Mutation -> createPost", () => { +describe("controllers -> post -> createPost", () => { afterEach(() => { - vi.doUnmock("../../../src/constants"); - vi.resetModules(); - vi.resetAllMocks(); + vi.clearAllMocks(); }); - it(`throws NotFoundError if no user exists with _id === context.userId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: "", - text: "", - videoUrl: "", - title: "", - }, - }; - - const context = { - userId: new Types.ObjectId().toString(), - }; - - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - - await createPostResolver?.({}, args, context); - expect.fail(); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, - ); - } + it("should throw NotFoundError if no user exists with _id === userId", async () => { + const req = { + userId: new Types.ObjectId().toString(), + body: { + organizationId: testOrganization?._id, + text: "Test post", + }, + } as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await createPost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + }); }); - it(`throws NotFoundError if no organization exists with _id === args.data.organizationId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: new Types.ObjectId().toString(), - text: "", - videoUrl: "", - title: "", - imageUrl: null, - }, - }; - - const context = { - userId: testUser?.id, - }; - - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - - await createPostResolver?.({}, args, context); - expect.fail(); - } catch (error) { - if (error instanceof Error) { - expect(spy).toBeCalledWith(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE); - expect(error.message).toEqual( - `Translated ${ORGANIZATION_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - } + it("should throw NotFoundError if no organization exists with _id === organizationId", async () => { + const req = { + userId: testUser?.id, + body: { + organizationId: new Types.ObjectId().toString(), + text: "Test post", + }, + } as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await createPost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${ORGANIZATION_NOT_FOUND_ERROR.MESSAGE}`, + }); }); - it("throws error if AppUserProfile is not found", async () => { + it("should throw UnauthorizedError if AppUserProfile is not found", async () => { const userWithoutProfileId = await createTestUser(); await AppUserProfile.findByIdAndDelete( userWithoutProfileId?.appUserProfileId, ); - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: "", - text: "", - videoUrl: "", - title: "", - }, - }; - - const context = { - userId: userWithoutProfileId?.id, - }; - - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - - await createPostResolver?.({}, args, context); - expect.fail(); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, - ); - } - }); - it(`throws USER_NOT_AUTHORIZED_TO_PIN ERROR if the user is not authorized to pin the post`, async () => { - const organizationWithNoAdmin = await createTestUserAndOrganization( - true, - false, - ); - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: organizationWithNoAdmin[1]?._id, - text: "New Post Text", - videoUrl: "http://dummyURL.com/", - title: "New Post Title", - imageUrl: "http://dummyURL.com/image/", - pinned: true, - }, - }; - - const context = { - userId: organizationWithNoAdmin[0]?.id, - }; - - expect(args.data.pinned).toBe(true); - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - - await createPostResolver?.({}, args, context); - expect.fail(); - } catch (error) { - if (error instanceof Error) { - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_TO_PIN.MESSAGE); - expect(error.message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_TO_PIN.MESSAGE}`, - ); - } - } + const req = { + userId: userWithoutProfileId?.id, + body: { + organizationId: testOrganization?._id, + text: "Test post", + }, + } as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await createPost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + }); }); - it("throws error if user is not member of the organization and is not superadmin", async () => { - const organizationWithNoMember = await createTestUserAndOrganization(false); - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: organizationWithNoMember[1]?._id, - text: "New Post Text", - videoUrl: "http://dummyURL.com/", - title: "New Post Title", - imageUrl: "http://dummyURL.com/image/", - pinned: true, - }, - }; - - const context = { - userId: organizationWithNoMember[0]?.id, - }; - - expect(args.data.pinned).toBe(true); - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - - await createPostResolver?.({}, args, context); - expect.fail(); - } catch (error) { - if (error instanceof Error) { - expect(spy).toBeCalledWith(USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE); - expect(error.message).toEqual( - `Translated ${USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE}`, - ); - } - } + it("should throw error if user is not member of the organization and is not superadmin", async () => { + const [nonMemberUser] = await createTestUserAndOrganization(false); + + const req = { + userId: nonMemberUser?.id, + body: { + organizationId: testOrganization?._id, + text: "Test post", + }, + } as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await createPost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE}`, + }); }); - it(`pinned post should be successfully added to the organization`, async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => `Translated ${message}`, + it("should throw error if trying to pin post without proper authorization", async () => { + const [nonAdminUser, nonAdminOrg] = await createTestUserAndOrganization( + true, + false, ); - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?.id, - text: "New Post Text", - videoUrl: "http://dummyURL.com/", - title: "New Post Title", - imageUrl: "http://dummyURL.com/image/", + const req = { + userId: nonAdminUser?.id, + body: { + organizationId: nonAdminOrg?._id, + text: "Test post", + title: "Test title", pinned: true, }, - }; - const context = { - userId: testUser?.id, - }; + } as InterfaceAuthenticatedRequest; - expect(args.data.pinned).toBe(true); + const res = mockResponse(); + await createPost(req, res); - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - const createdPost = await createPostResolver?.({}, args, context); - expect(createdPost).toEqual( - expect.objectContaining({ - text: "New Post Text", - videoUrl: null, // Update the expected value to match the received value - title: "New Post Title", + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${USER_NOT_AUTHORIZED_TO_PIN.MESSAGE}`, + }); + }); + + it("should throw error if pinned post has no title", async () => { + const req = { + userId: testUser?.id, + body: { + organizationId: testOrganization?._id, + text: "Test post", pinned: true, - }), - ); + }, + } as InterfaceAuthenticatedRequest; - const updatedTestOrg = await Organization.findOne({ - _id: testOrganization?.id, - }).lean(); + const res = mockResponse(); + await createPost(req, res); - expect( - updatedTestOrg?.pinnedPosts - .map((id) => id.toString()) - .includes(createdPost?._id.toString()), - ).toBeTruthy(); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${PLEASE_PROVIDE_TITLE.MESSAGE}`, + }); }); - it(`creates the post and returns it when image or video is not provided`, async () => { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?.id, - text: "text", - videoUrl: "videoUrl", - title: "title", + it("should throw error if title exceeds maximum length", async () => { + const req = { + userId: testUser?.id, + body: { + organizationId: testOrganization?._id, + title: "a".repeat(257), + text: "Test post", pinned: true, }, - }; + } as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await createPost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, + }); + }); - const context = { + it("should throw error if text exceeds maximum length", async () => { + const req = { userId: testUser?.id, - }; + body: { + organizationId: testOrganization?._id, + title: "Test title", + text: "a".repeat(501), + pinned: true, + }, + } as InterfaceAuthenticatedRequest; - expect(args.data.pinned).toBe(true); + const res = mockResponse(); + await createPost(req, res); - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in information`, + }); + }); + + it("should successfully create a regular post", async () => { + const req = { + userId: testUser?.id, + body: { + organizationId: testOrganization?._id, + text: "Test post", + }, + } as InterfaceAuthenticatedRequest; - const createPostPayload = await createPostResolver?.({}, args, context); - expect(createPostPayload?.pinned).toBe(true); + const res = mockResponse(); + await createPost(req, res); - expect(createPostPayload).toEqual( + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ - title: "title", - videoUrl: null, // Update the expected value to match the received value - creatorId: testUser?._id, - organization: testOrganization?._id, - imageUrl: null, + post: expect.objectContaining({ + text: "Test post", + pinned: false, + }), }), ); }); - it(`creates the post and and returns it when an image is provided`, async () => { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?.id, - text: "text", - title: "title", + it("should successfully create a pinned post", async () => { + const req = { + userId: testUser?.id, + body: { + organizationId: testOrganization?._id, + title: "Test pinned post", + text: "Test post content", pinned: true, }, - file: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAZSURBVBhXYzxz5sx/BiBgefLkCQMbGxsDAEdkBicg9wbaAAAAAElFTkSuQmCC", // Provide a supported file type - }; + } as InterfaceAuthenticatedRequest; - vi.spyOn(uploadEncodedImage, "uploadEncodedImage").mockImplementation( - async (encodedImageURL: string) => encodedImageURL, - ); - - const context = { - userId: testUser?.id, - apiRootUrl: BASE_URL, - }; + const res = mockResponse(); + await createPost(req, res); - expect(args.data.pinned).toBe(true); - - const createPostPayload = await createPostResolverImage?.( - {}, - args, - context, + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + post: expect.objectContaining({ + title: "Test pinned post", + text: "Test post content", + pinned: true, + }), + }), ); - expect(createPostPayload?.pinned).toBe(true); - - const testCreatePostPayload = await Post.findOne({ - _id: createPostPayload?._id, - imageUrl: { $ne: null }, - }).lean(); - //Ensures that the post is created and imageUrl is not null - expect(testCreatePostPayload).not.toBeNull(); + // Verify organization was updated with pinned post + const updatedOrg = await Organization.findById(testOrganization?._id); + const createdPost = await Post.findOne({ title: "Test pinned post" }); + expect(updatedOrg?.pinnedPosts).toContainEqual(createdPost?._id); }); - it(`creates the post and and returns it when a video is provided`, async () => { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?.id, - text: "text", - title: "title", - pinned: true, - }, - file: "data:video/mp4;base64,VIDEO_BASE64_DATA_HERE", // Provide a supported file type + it("should successfully create a post with file upload", async () => { + const mockFileId = new Types.ObjectId(); + const mockFileUploadResponse: InterfaceUploadedFileResponse = { + _id: mockFileId, + uri: "test/file/path", + visibility: "PUBLIC", + objectKey: "test-object-key", }; - vi.spyOn(uploadEncodedVideo, "uploadEncodedVideo").mockImplementation( - async (uploadEncodedVideo: string) => uploadEncodedVideo, + vi.spyOn(uploadFileService, "uploadFile").mockResolvedValueOnce( + mockFileUploadResponse, ); - const context = { + const req = { userId: testUser?.id, - apiRootUrl: BASE_URL, - }; + body: { + organizationId: testOrganization?._id, + text: "Test post with file", + }, + file: { + filename: "test.jpg", + mimetype: "image/jpeg", + buffer: Buffer.from("test"), + originalname: "test.jpg", + size: 1024, + }, + } as InterfaceAuthenticatedRequest; - expect(args.data.pinned).toBe(true); + const res = mockResponse(); + await createPost(req, res); - const createPostPayload = await createPostResolverImage?.( - {}, - args, - context, + expect(uploadFileService.uploadFile).toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + post: expect.objectContaining({ + text: "Test post with file", + file: mockFileId, + }), + }), ); - expect(createPostPayload?.pinned).toBe(true); - - const testCreatePostPayload = await Post.findOne({ - _id: createPostPayload?._id, - videoUrl: { $ne: null }, - }).lean(); - - //Ensures that the post is created and videoUrl is not null - expect(testCreatePostPayload).not.toBeNull(); }); - it(`creates the post and throws an error for unsupported file type`, async () => { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?.id, - text: "text", - videoUrl: "videoUrl", - title: "title", - pinned: true, - }, - file: "unsupportedFile.txt", // Provide an unsupported file type - }; + it("should handle file upload error gracefully", async () => { + vi.spyOn(uploadFileService, "uploadFile").mockRejectedValueOnce( + new Error("Upload failed"), + ); - const context = { + const req = { userId: testUser?.id, - apiRootUrl: BASE_URL, - }; + body: { + organizationId: testOrganization?._id, + text: "Test post with file", + }, + file: { + filename: "test.jpg", + mimetype: "image/jpeg", + buffer: Buffer.from("test"), + originalname: "test.jpg", + size: 1024, + }, + } as InterfaceAuthenticatedRequest; - expect(args.data.pinned).toBe(true); + const res = mockResponse(); + await createPost(req, res); - // Mock the uploadEncodedImage function to throw an error for unsupported file types - vi.spyOn(uploadEncodedImage, "uploadEncodedImage").mockImplementation( - () => { - throw new Error("Unsupported file type."); + expect(uploadFileService.uploadFile).toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: "Upload failed", + }); + }); + + it("should handle invalid file type", async () => { + const req = { + userId: testUser?.id, + body: { + organizationId: testOrganization?._id, + text: "Test post with file", }, - ); + file: { + filename: "test.exe", + mimetype: "application/x-msdownload", + buffer: Buffer.from("test"), + originalname: "test.exe", + size: 1024, + }, + } as InterfaceAuthenticatedRequest; - // Ensure that an error is thrown when createPostResolverImage is called - await expect( - createPostResolverImage?.({}, args, context), - ).rejects.toThrowError("Unsupported file type."); - }); + const res = mockResponse(); + await createPost(req, res); - it(`throws String Length Validation error if title is greater than 256 characters`, async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, - ); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?._id, - text: "random", - videoUrl: "", - title: - "AfGtN9o7IJXH9Xr5P4CcKTWMVWKOOHTldleLrWfZcThgoX5scPE5o0jARvtVA8VhneyxXquyhWb5nluW2jtP0Ry1zIOUFYfJ6BUXvpo4vCw4GVleGBnoKwkFLp5oW9L8OsEIrjVtYBwaOtXZrkTEBySZ1prr0vFcmrSoCqrCTaChNOxL3tDoHK6h44ChFvgmoVYMSq3IzJohKtbBn68D9NfEVMEtoimkGarUnVBAOsGkKv0mIBJaCl2pnR8Xwq1cG1", - imageUrl: null, - pinned: true, - }, - }; - - const context = { - userId: testUser?.id, - }; - expect(args.data.pinned).toBe(true); - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - - const createdPost = await createPostResolver?.({}, args, context); - expect(createdPost?.pinned).toBe(true); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, - ); - } - } - }); - it(`throws String Length Validation error if text is greater than 500 characters`, async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, - ); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?._id, - text: "JWQPfpdkGGGKyryb86K4YN85nDj4m4F7gEAMBbMXLax73pn2okV6kpWY0EYO0XSlUc0fAlp45UCgg3s6mqsRYF9FOlzNIDFLZ1rd03Z17cdJRuvBcAmbC0imyqGdXHGDUQmVyOjDkaOLAvjhB5uDeuEqajcAPTcKpZ6LMpigXuqRAd0xGdPNXyITC03FEeKZAjjJL35cSIUeMv5eWmiFlmmm70FU1Bp6575zzBtEdyWPLflcA2GpGmmf4zvT7nfgN3NIkwQIhk9OwP8dn75YYczcYuUzLpxBu1Lyog77YlAj5DNdTIveXu9zHeC6V4EEUcPQtf1622mhdU3jZNMIAyxcAG4ErtztYYRqFs0ApUxXiQI38rmiaLcicYQgcOxpmFvqRGiSduiCprCYm90CHWbQFq4w2uhr8HhR3r9HYMIYtrRyO6C3rPXaQ7otpjuNgE0AKI57AZ4nGG1lvNwptFCY60JEndSLX9Za6XP1zkVRLaMZArQNl", - videoUrl: "", - title: "random", - imageUrl: null, - pinned: true, - }, - }; - - const context = { - userId: testUser?.id, - }; - - expect(args.data.pinned).toBe(true); - - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - - const createdPost = await createPostResolver?.({}, args, context); - expect(createdPost?.pinned).toBe(true); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in information`, - ); - } - } + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${INVALID_FILE_TYPE.MESSAGE}`, + }); }); - it("throws an error if the user tries to create a post but post is not pinned", async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, - ); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?._id, - text: "text", - pinned: false, - }, - }; - - const context = { - userId: testUser?.id, - }; - - expect(args.data.pinned).toBe(false); - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - const createdPost = await createPostResolver?.({}, args, context); - expect(createdPost?.pinned).toBe(false); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toEqual( - `Cannot create post when pinned is false`, - ); - } - } - }); + it("should handle non-Error type errors with internal server error message", async () => { + // Mock User.findOne to throw a non-Error type error + vi.spyOn(Post, "create").mockImplementationOnce(() => { + throw "Some unknown error"; + }); - it("throws error if title is provided and post is not pinned", async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, - ); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?._id, - title: "Test title", - text: "Test text", - pinned: false, - }, - }; - - const context = { - userId: testUser?.id, - }; - - expect(args.data.pinned).toBe(false); - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - const createdPost = await createPostResolver?.({}, args, context); - expect(createdPost?.pinned).toBe(false); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toEqual( - `Post needs to be pinned inorder to add a title`, - ); - } - } - }); + const req = { + userId: testUser?.id, + body: { + organizationId: testOrganization?._id, + text: "Test post", + }, + } as InterfaceAuthenticatedRequest; - it("throws error if title is not provided and post is pinned", async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, - ); - try { - const args: MutationCreatePostArgs = { - data: { - organizationId: testOrganization?._id, - title: "", - text: "Test text", - pinned: true, - }, - }; - - const context = { - userId: testUser?.id, - }; - expect(args.data.pinned).toBe(true); - - const { createPost: createPostResolver } = await import( - "../../../src/resolvers/Mutation/createPost" - ); - const createPost = await createPostResolver?.({}, args, context); - expect(createPost?.pinned).toBe(true); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toEqual(`Please provide a title to pin post`); - } - } + const res = mockResponse(); + await createPost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${INTERNAL_SERVER_ERROR.MESSAGE}`, + }); }); }); diff --git a/tests/resolvers/Mutation/createVolunteerMembership.spec.ts b/tests/resolvers/Mutation/createVolunteerMembership.spec.ts new file mode 100644 index 00000000000..6ef779bbc6f --- /dev/null +++ b/tests/resolvers/Mutation/createVolunteerMembership.spec.ts @@ -0,0 +1,165 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import type { Document } from "mongoose"; +import { Types } from "mongoose"; +import type { MutationCreateVolunteerMembershipArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; +import { + EVENT_NOT_FOUND_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../../src/constants"; +import type { TestUserType } from "../../helpers/userAndOrg"; +import { createTestEvent } from "../../helpers/events"; +import type { TestEventType } from "../../helpers/events"; +import { createTestUser } from "../../helpers/user"; +import type { InterfaceEventVolunteerGroup } from "../../../src/models"; +import { createVolunteerMembership } from "../../../src/resolvers/Mutation/createVolunteerMembership"; +import type { + TestVolunteerGroupType, + TestVolunteerType, +} from "../../helpers/volunteers"; +import { createTestVolunteerAndGroup } from "../../helpers/volunteers"; + +export type TestEventVolunteerGroupType = + | (InterfaceEventVolunteerGroup & Document) + | null; + +let testUser1: TestUserType; +let testEvent: TestEventType; +let tUser: TestUserType; +let tEvent: TestEventType; +let tVolunteer: TestVolunteerType; +let tVolunteerGroup: TestVolunteerGroupType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const { requestContext } = await import("../../../src/libraries"); + vi.spyOn(requestContext, "translate").mockImplementation( + (message) => message, + ); + testUser1 = await createTestUser(); + [, , testEvent] = await createTestEvent(); + + [tUser, , tEvent, tVolunteer, tVolunteerGroup] = + await createTestVolunteerAndGroup(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Mutation -> createVolunteerMembership", () => { + afterEach(() => { + vi.doUnmock("../../../src/constants"); + vi.resetModules(); + }); + + it(`throws NotFoundError if no user exists with _id === context.userId`, async () => { + try { + const args: MutationCreateVolunteerMembershipArgs = { + data: { + event: tEvent?._id, + group: tVolunteerGroup?._id, + status: "invited", + userId: tUser?._id, + }, + }; + + const context = { + userId: new Types.ObjectId().toString(), + }; + + await createVolunteerMembership?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`throws NotFoundError if no volunteer user exists with _id === args.data.userId`, async () => { + try { + const args: MutationCreateVolunteerMembershipArgs = { + data: { + event: tEvent?._id, + group: tVolunteerGroup?._id, + status: "invited", + userId: new Types.ObjectId().toString(), + }, + }; + + const context = { + userId: tUser?._id, + }; + + await createVolunteerMembership?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`throws NotFoundError if no event exists with _id === args.data.event`, async () => { + try { + const args: MutationCreateVolunteerMembershipArgs = { + data: { + event: new Types.ObjectId().toString(), + group: tVolunteerGroup?._id, + status: "invited", + userId: tUser?._id, + }, + }; + + const context = { + userId: tUser?._id, + }; + + await createVolunteerMembership?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual(EVENT_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`Create Voluneer Membership when volunteer already exists`, async () => { + const args: MutationCreateVolunteerMembershipArgs = { + data: { + event: tEvent?._id, + group: tVolunteerGroup?._id, + status: "invited", + userId: tUser?._id, + }, + }; + + const context = { + userId: tUser?._id, + }; + + const mem = await createVolunteerMembership?.({}, args, context); + expect(mem?.volunteer).toEqual(tVolunteer?._id); + }); + + it(`Create Voluneer Membership when volunteer doesn't exists`, async () => { + const args: MutationCreateVolunteerMembershipArgs = { + data: { + event: testEvent?._id, + status: "invited", + userId: testUser1?._id, + }, + }; + + const context = { + userId: tUser?._id, + }; + + const mem = await createVolunteerMembership?.({}, args, context); + expect(mem?.event).toEqual(testEvent?._id); + }); +}); diff --git a/tests/resolvers/Mutation/login.spec.ts b/tests/resolvers/Mutation/login.spec.ts index 120784dce82..f8335c9782e 100644 --- a/tests/resolvers/Mutation/login.spec.ts +++ b/tests/resolvers/Mutation/login.spec.ts @@ -84,6 +84,99 @@ describe("resolvers -> Mutation -> login", () => { vi.resetModules(); }); + it("throws NotFoundError if the user is not found after creating AppUserProfile", async () => { + const { requestContext } = await import("../../../src/libraries"); + + // Spy on the translate function to capture error messages + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + // Create a new user with a unique email + const newUser = await User.create({ + email: `nonexistentuser${nanoid().toLowerCase()}@gmail.com`, + password: "password", + firstName: "John", + lastName: "Doe", + }); + + // Delete the user immediately to simulate a missing user scenario after AppUserProfile creation + await User.deleteOne({ _id: newUser._id }); + + // Prepare the arguments for the login resolver + const args: MutationLoginArgs = { + data: { + email: newUser.email, + password: "password", + }, + }; + + // Call the login resolver, which should throw an error + const { login: loginResolver } = await import( + "../../../src/resolvers/Mutation/login" + ); + + await loginResolver?.({}, args, {}); + } catch (error: unknown) { + if (error instanceof Error) { + // Verify that the translate function was called with the correct error message + expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); + // Verify that the error message is correctly translated + expect(error.message).toEqual( + `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + ); + } + } + }); + + it("creates a new AppUserProfile for the user if it doesn't exist and associates it with the user", async () => { + // Create a new user without an associated AppUserProfile + const newUser = await User.create({ + email: `email${nanoid().toLowerCase()}@gmail.com`, + password: "password", + firstName: "firstName", + lastName: "lastName", + }); + + const hashedPassword = await bcrypt.hash("password", 12); + await User.updateOne( + { + _id: newUser?._id, + }, + { + $set: { + password: hashedPassword, + }, + }, + ); + + const args: MutationLoginArgs = { + data: { + email: newUser?.email, + password: "password", + }, + }; + + // Call the login resolver + const loginPayload = await loginResolver?.({}, args, {}); + + // Find the newly created AppUserProfile + const userAppProfile = await AppUserProfile.findOne({ + userId: newUser?._id, + }); + + // Check if the AppUserProfile is created and associated with the user + expect(userAppProfile).toBeDefined(); + expect(loginPayload).toEqual( + expect.objectContaining({ + user: expect.objectContaining({ + appUserProfileId: userAppProfile?._id, + }), + }), + ); + }); + it(`throws NotFoundError if no user exists with email === args.data.email`, async () => { const { requestContext } = await import("../../../src/libraries"); diff --git a/tests/resolvers/Mutation/removeDirectChat.spec.ts b/tests/resolvers/Mutation/removeDirectChat.spec.ts deleted file mode 100644 index 62bf2af6c86..00000000000 --- a/tests/resolvers/Mutation/removeDirectChat.spec.ts +++ /dev/null @@ -1,228 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { - Organization, - DirectChat, - DirectChatMessage, -} from "../../../src/models"; -import type { MutationRemoveDirectChatArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { - ORGANIZATION_NOT_FOUND_ERROR, - CHAT_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ADMIN, -} from "../../../src/constants"; -import { - beforeAll, - afterAll, - describe, - it, - expect, - vi, - afterEach, -} from "vitest"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; -import type { TestDirectChatType } from "../../helpers/directChat"; -import { createTestDirectChat } from "../../helpers/directChat"; -import { cacheOrganizations } from "../../../src/services/OrganizationCache/cacheOrganizations"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testOrganization: TestOrganizationType; -let testDirectChat: TestDirectChatType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestDirectChat(); - testUser = temp[0]; - testOrganization = temp[1]; - - testDirectChat = await DirectChat.create({ - users: [testUser?._id], - creatorId: testUser?._id, - organization: testOrganization?._id, - }); - - const testDirectChatMessage = temp[2]; - - testDirectChat = await DirectChat.findOneAndUpdate( - { - _id: testDirectChat._id, - }, - { - $push: { - messages: testDirectChatMessage?._id, - }, - }, - { - new: true, - }, - ); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> removeDirectChat", () => { - afterEach(() => { - vi.doUnmock("../../../src/constants"); - vi.resetModules(); - }); - - it(`throws NotFoundError if no organization exists with _id === args.organizationId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementation((message) => `Translated ${message}`); - - try { - const args: MutationRemoveDirectChatArgs = { - chatId: "", - organizationId: new Types.ObjectId().toString(), - }; - - const context = { - userId: testUser?.id, - }; - - const { removeDirectChat: removeDirectChatResolver } = await import( - "../../../src/resolvers/Mutation/removeDirectChat" - ); - await removeDirectChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${ORGANIZATION_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - - it(`throws NotFoundError if no directChat exists with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementation((message) => `Translated ${message}`); - - try { - const args: MutationRemoveDirectChatArgs = { - chatId: new Types.ObjectId().toString(), - organizationId: testOrganization?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { removeDirectChat: removeDirectChatResolver } = await import( - "../../../src/resolvers/Mutation/removeDirectChat" - ); - await removeDirectChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${CHAT_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - - it(`throws UnauthorizedError if user with _id === context.userId is not an admin - of organization with _id === args.organizationId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const updatedOrganization = await Organization.findOneAndUpdate( - { - _id: testOrganization?._id, - }, - { - $set: { - admins: [], - }, - }, - { - new: true, - }, - ); - - if (updatedOrganization !== null) { - await cacheOrganizations([updatedOrganization]); - } - - const args: MutationRemoveDirectChatArgs = { - chatId: testDirectChat?.id, - organizationId: testOrganization?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { removeDirectChat: removeDirectChatResolver } = await import( - "../../../src/resolvers/Mutation/removeDirectChat" - ); - await removeDirectChatResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ADMIN.MESSAGE}`, - ); - - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ADMIN.MESSAGE); - } - }); - - it(`deletes the directChat with _id === args.chatId`, async () => { - const updatedOrganization = await Organization.findOneAndUpdate( - { - _id: testOrganization?._id, - }, - { - $push: { - admins: testUser?._id, - }, - }, - { - new: true, - }, - ); - - if (updatedOrganization !== null) { - await cacheOrganizations([updatedOrganization]); - } - - const args: MutationRemoveDirectChatArgs = { - chatId: testDirectChat?.id, - organizationId: testOrganization?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { removeDirectChat: removeDirectChatResolver } = await import( - "../../../src/resolvers/Mutation/removeDirectChat" - ); - const removeDirectChatPayload = await removeDirectChatResolver?.( - {}, - args, - context, - ); - - expect(removeDirectChatPayload).toEqual(testDirectChat?.toObject()); - - const testDeletedDirectChatMessages = await DirectChatMessage.find({ - directChatMessageBelongsTo: testDirectChat?._id, - }).lean(); - - expect(testDeletedDirectChatMessages).toEqual([]); - }); -}); diff --git a/tests/resolvers/Mutation/removeEventVolunteer.spec.ts b/tests/resolvers/Mutation/removeEventVolunteer.spec.ts index bc6aa6d8917..30c8bb41420 100644 --- a/tests/resolvers/Mutation/removeEventVolunteer.spec.ts +++ b/tests/resolvers/Mutation/removeEventVolunteer.spec.ts @@ -25,6 +25,7 @@ import { createTestEvent } from "../../helpers/events"; import { EventVolunteer, EventVolunteerGroup } from "../../../src/models"; import { createTestUser } from "../../helpers/user"; import type { TestEventVolunteerGroupType } from "./createEventVolunteer.spec"; +import { removeEventVolunteer } from "../../../src/resolvers/Mutation/removeEventVolunteer"; let MONGOOSE_INSTANCE: typeof mongoose; let testUser: TestUserType; @@ -35,23 +36,27 @@ let testGroup: TestEventVolunteerGroupType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); + const { requestContext } = await import("../../../src/libraries"); + vi.spyOn(requestContext, "translate").mockImplementation( + (message) => message, + ); testUser = await createTestUser(); [eventAdminUser, , testEvent] = await createTestEvent(); testGroup = await EventVolunteerGroup.create({ - creatorId: eventAdminUser?._id, - eventId: testEvent?._id, - leaderId: eventAdminUser?._id, + creator: eventAdminUser?._id, + event: testEvent?._id, + leader: eventAdminUser?._id, name: "Test group", }); testEventVolunteer = await EventVolunteer.create({ - creatorId: eventAdminUser?._id, - userId: testUser?._id, - eventId: testEvent?._id, - groupId: testGroup._id, - isInvited: true, - isAssigned: false, + creator: eventAdminUser?._id, + user: testUser?._id, + event: testEvent?._id, + groups: [testGroup?._id], + hasAccepted: false, + isPublic: false, }); }); @@ -64,13 +69,33 @@ describe("resolvers -> Mutation -> removeEventVolunteer", () => { vi.doUnmock("../../../src/constants"); vi.resetModules(); }); - it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); + it(`removes event volunteer with _id === args.id and returns it`, async () => { + const args: MutationUpdateEventVolunteerArgs = { + id: testEventVolunteer?._id, + }; + + const context = { userId: eventAdminUser?._id }; + + const deletedVolunteer = await removeEventVolunteer?.({}, args, context); + + const updatedGroup = await EventVolunteerGroup.findOne({ + _id: testGroup?._id, + }); + + expect(updatedGroup?.volunteers.toString()).toEqual(""); + expect(deletedVolunteer).toEqual( + expect.objectContaining({ + _id: testEventVolunteer?._id, + user: testEventVolunteer?.user, + hasAccepted: testEventVolunteer?.hasAccepted, + isPublic: testEventVolunteer?.isPublic, + }), + ); + }); + + it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { try { const args: MutationUpdateEventVolunteerArgs = { id: testEventVolunteer?._id, @@ -78,25 +103,15 @@ describe("resolvers -> Mutation -> removeEventVolunteer", () => { const context = { userId: new Types.ObjectId().toString() }; - const { removeEventVolunteer: removeEventVolunteerResolver } = - await import("../../../src/resolvers/Mutation/removeEventVolunteer"); - - await removeEventVolunteerResolver?.({}, args, context); + await removeEventVolunteer?.({}, args, context); } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + `${USER_NOT_FOUND_ERROR.MESSAGE}`, ); } }); it(`throws NotFoundError if no event volunteer exists with _id === args.id`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { const args: MutationUpdateEventVolunteerArgs = { id: new Types.ObjectId().toString(), @@ -104,76 +119,35 @@ describe("resolvers -> Mutation -> removeEventVolunteer", () => { const context = { userId: testUser?._id }; - const { removeEventVolunteer: removeEventVolunteerResolver } = - await import("../../../src/resolvers/Mutation/removeEventVolunteer"); - - await removeEventVolunteerResolver?.({}, args, context); + await removeEventVolunteer?.({}, args, context); } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith( - EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE, - ); expect((error as Error).message).toEqual( - `Translated ${EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE}`, + `${EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE}`, ); } }); it(`throws UnauthorizedError if current user is not leader of group`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { + const newVolunteer = await EventVolunteer.create({ + creator: eventAdminUser?._id, + user: testUser?._id, + event: testEvent?._id, + groups: [testGroup?._id], + hasAccepted: false, + isPublic: false, + }); const args: MutationUpdateEventVolunteerArgs = { - id: testEventVolunteer?._id, + id: newVolunteer?._id.toString(), }; const context = { userId: testUser?._id }; - const { removeEventVolunteer: removeEventVolunteerResolver } = - await import("../../../src/resolvers/Mutation/removeEventVolunteer"); - - await removeEventVolunteerResolver?.({}, args, context); + await removeEventVolunteer?.({}, args, context); } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + `${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, ); } }); - - it(`removes event volunteer with _id === args.id and returns it`, async () => { - const args: MutationUpdateEventVolunteerArgs = { - id: testEventVolunteer?._id, - }; - - const context = { userId: eventAdminUser?._id }; - const { removeEventVolunteer: removeEventVolunteerResolver } = await import( - "../../../src/resolvers/Mutation/removeEventVolunteer" - ); - - const deletedVolunteer = await removeEventVolunteerResolver?.( - {}, - args, - context, - ); - - const updatedGroup = await EventVolunteerGroup.findOne({ - _id: testGroup?._id, - }); - - expect(updatedGroup?.volunteers.toString()).toEqual(""); - - expect(deletedVolunteer).toEqual( - expect.objectContaining({ - _id: testEventVolunteer?._id, - userId: testEventVolunteer?.userId, - isInvited: testEventVolunteer?.isInvited, - isAssigned: testEventVolunteer?.isAssigned, - response: testEventVolunteer?.response, - }), - ); - }); }); diff --git a/tests/resolvers/Mutation/removeEventVolunteerGroup.spec.ts b/tests/resolvers/Mutation/removeEventVolunteerGroup.spec.ts index f29ca33ecd8..784afb8394e 100644 --- a/tests/resolvers/Mutation/removeEventVolunteerGroup.spec.ts +++ b/tests/resolvers/Mutation/removeEventVolunteerGroup.spec.ts @@ -6,6 +6,7 @@ import { USER_NOT_FOUND_ERROR, EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, + EVENT_NOT_FOUND_ERROR, } from "../../../src/constants"; import { beforeAll, @@ -22,6 +23,7 @@ import { createTestEvent } from "../../helpers/events"; import { EventVolunteer, EventVolunteerGroup } from "../../../src/models"; import { createTestUser } from "../../helpers/user"; import type { TestEventVolunteerGroupType } from "./createEventVolunteer.spec"; +import { removeEventVolunteerGroup } from "../../../src/resolvers/Mutation/removeEventVolunteerGroup"; let MONGOOSE_INSTANCE: typeof mongoose; let testUser: TestUserType; @@ -31,23 +33,27 @@ let testGroup: TestEventVolunteerGroupType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); + const { requestContext } = await import("../../../src/libraries"); + vi.spyOn(requestContext, "translate").mockImplementation( + (message) => message, + ); testUser = await createTestUser(); [eventAdminUser, , testEvent] = await createTestEvent(); testGroup = await EventVolunteerGroup.create({ - creatorId: eventAdminUser?._id, - eventId: testEvent?._id, - leaderId: eventAdminUser?._id, + creator: eventAdminUser?._id, + event: testEvent?._id, + leader: eventAdminUser?._id, name: "Test group", }); await EventVolunteer.create({ - creatorId: eventAdminUser?._id, - userId: testUser?._id, - eventId: testEvent?._id, - groupId: testGroup._id, - isInvited: true, - isAssigned: false, + creator: eventAdminUser?._id, + user: testUser?._id, + event: testEvent?._id, + groups: [testGroup._id], + hasAccepted: false, + isPublic: false, }); }); @@ -61,12 +67,6 @@ describe("resolvers -> Mutation -> removeEventVolunteerGroup", () => { vi.resetModules(); }); it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { const args: MutationUpdateEventVolunteerArgs = { id: testGroup?._id, @@ -74,27 +74,20 @@ describe("resolvers -> Mutation -> removeEventVolunteerGroup", () => { const context = { userId: new Types.ObjectId().toString() }; - const { removeEventVolunteerGroup: removeEventVolunteerGroupResolver } = + const { removeEventVolunteerGroup: removeEventVolunteerGroup } = await import( "../../../src/resolvers/Mutation/removeEventVolunteerGroup" ); - await removeEventVolunteerGroupResolver?.({}, args, context); + await removeEventVolunteerGroup?.({}, args, context); } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + `${USER_NOT_FOUND_ERROR.MESSAGE}`, ); } }); it(`throws NotFoundError if no event volunteer group exists with _id === args.id`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { const args: MutationUpdateEventVolunteerArgs = { id: new Types.ObjectId().toString(), @@ -102,29 +95,15 @@ describe("resolvers -> Mutation -> removeEventVolunteerGroup", () => { const context = { userId: testUser?._id }; - const { removeEventVolunteerGroup: removeEventVolunteerGroupResolver } = - await import( - "../../../src/resolvers/Mutation/removeEventVolunteerGroup" - ); - - await removeEventVolunteerGroupResolver?.({}, args, context); + await removeEventVolunteerGroup?.({}, args, context); } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith( - EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE, - ); expect((error as Error).message).toEqual( - `Translated ${EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE}`, + `${EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE}`, ); } }); it(`throws UnauthorizedError if current user is not an event admin`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { const args: MutationUpdateEventVolunteerArgs = { id: testGroup?._id, @@ -132,16 +111,10 @@ describe("resolvers -> Mutation -> removeEventVolunteerGroup", () => { const context = { userId: testUser?._id }; - const { removeEventVolunteerGroup: removeEventVolunteerGroupResolver } = - await import( - "../../../src/resolvers/Mutation/removeEventVolunteerGroup" - ); - - await removeEventVolunteerGroupResolver?.({}, args, context); + await removeEventVolunteerGroup?.({}, args, context); } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + `${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, ); } }); @@ -152,10 +125,8 @@ describe("resolvers -> Mutation -> removeEventVolunteerGroup", () => { }; const context = { userId: eventAdminUser?._id }; - const { removeEventVolunteerGroup: removeEventVolunteerGroupResolver } = - await import("../../../src/resolvers/Mutation/removeEventVolunteerGroup"); - const deletedVolunteerGroup = await removeEventVolunteerGroupResolver?.( + const deletedVolunteerGroup = await removeEventVolunteerGroup?.( {}, args, context, @@ -164,11 +135,34 @@ describe("resolvers -> Mutation -> removeEventVolunteerGroup", () => { expect(deletedVolunteerGroup).toEqual( expect.objectContaining({ _id: testGroup?._id, - leaderId: testGroup?.leaderId, + leader: testGroup?.leader, name: testGroup?.name, - creatorId: testGroup?.creatorId, - eventId: testGroup?.eventId, + creator: testGroup?.creator, + event: testGroup?.event, }), ); }); + + it(`throws NotFoundError if volunteerGroup.event doesn't exist`, async () => { + try { + const newGrp = await EventVolunteerGroup.create({ + creator: eventAdminUser?._id, + event: new Types.ObjectId(), + leader: eventAdminUser?._id, + name: "Test group", + }); + + const args: MutationUpdateEventVolunteerArgs = { + id: newGrp?._id.toString(), + }; + + const context = { userId: eventAdminUser?._id }; + + await removeEventVolunteerGroup?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + `${EVENT_NOT_FOUND_ERROR.MESSAGE}`, + ); + } + }); }); diff --git a/tests/resolvers/Mutation/removeFromUserTags.spec.ts b/tests/resolvers/Mutation/removeFromUserTags.spec.ts new file mode 100644 index 00000000000..76069c8c386 --- /dev/null +++ b/tests/resolvers/Mutation/removeFromUserTags.spec.ts @@ -0,0 +1,416 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import type { + MutationAssignToUserTagsArgs, + MutationRemoveFromUserTagsArgs, +} from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; +import { + TAG_NOT_FOUND, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../../src/constants"; +import { + AppUserProfile, + OrganizationTagUser, + TagUser, + User, +} from "../../../src/models"; +import type { TestUserTagType } from "../../helpers/tags"; +import { + createRootTagsWithOrg, + createTwoLevelTagsWithOrg, +} from "../../helpers/tags"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; +import { createTestUser } from "../../helpers/userAndOrg"; + +let MONGOOSE_INSTANCE: typeof mongoose; + +let adminUser: TestUserType; +let adminUser2: TestUserType; +let testTag2: TestUserTagType; +let testTag3: TestUserTagType; +let testTag: TestUserTagType; +let testSubTag1: TestUserTagType; +let testOrg1: TestOrganizationType; +let testOrg2: TestOrganizationType; +let randomUser1: TestUserType; +let randomUser2: TestUserType; +let randomUser3: TestUserType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [adminUser, testOrg1, [testTag, testSubTag1]] = + await createTwoLevelTagsWithOrg(); + [adminUser2, testOrg2, [testTag2, testTag3]] = await createRootTagsWithOrg(2); + randomUser1 = await createTestUser(); + randomUser2 = await createTestUser(); + randomUser3 = await createTestUser(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Mutation -> removeFromUserTags", () => { + afterEach(() => { + vi.doUnmock("../../../src/constants"); + vi.resetModules(); + vi.resetAllMocks(); + }); + + it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: new Types.ObjectId().toString() }, + expectedError: USER_NOT_FOUND_ERROR.MESSAGE, + }); + }); + + it(`throws NotFoundError if no tag exists with _id === args.input.currentTagId `, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: adminUser?._id, + }, + }, + context: { userId: adminUser?._id }, + expectedError: TAG_NOT_FOUND.MESSAGE, + }); + }); + + it(`throws Not Authorized Error if the current user is not a superadmin or admin of the organization `, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: randomUser1?._id }, + expectedError: USER_NOT_AUTHORIZED_ERROR.MESSAGE, + }); + }); + + it(`throws NotFoundError if one of the selected tags doesn't exist `, async () => { + await testErrorScenario({ + args: { + input: { + selectedTagIds: [ + testTag?._id.toString() ?? "", + new Types.ObjectId().toString(), + ], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: adminUser?._id }, + expectedError: TAG_NOT_FOUND.MESSAGE, + }); + }); + + it("throws error if user does not have appUserProfile", async () => { + const temp = await createTestUser(); + await AppUserProfile.deleteOne({ + userId: temp?._id, + }); + + await testErrorScenario({ + args: { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: testTag?._id.toString() ?? "", + }, + }, + context: { userId: temp?._id }, + expectedError: USER_NOT_AUTHORIZED_ERROR.MESSAGE, + }); + }); + + it(`Tag removal should be successful and the tag is returned`, async () => { + // assign testTag2 to random users + await Promise.all([ + User.findOneAndUpdate( + { + _id: randomUser2?._id, + }, + { + joinedOrganizations: testOrg2, + }, + ), + User.findOneAndUpdate( + { + _id: randomUser3?._id, + }, + { + joinedOrganizations: testOrg2, + }, + ), + TagUser.create({ + userId: randomUser2?._id, + tagId: testTag2?._id, + organizationId: testTag2?.organizationId, + }), + TagUser.create({ + userId: randomUser3?._id, + tagId: testTag2?._id, + organizationId: testTag2?.organizationId, + }), + ]); + + // now assign them to a new tag with the help of assignToUserTags mutation + const assignToUserTagsArgs: MutationAssignToUserTagsArgs = { + input: { + selectedTagIds: [testTag3?._id.toString() ?? ""], + currentTagId: testTag2?._id.toString() ?? "", + }, + }; + + const assignToUserTagsContext = { + userId: adminUser2?._id, + }; + + const { assignToUserTags: assignToUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/assignToUserTags" + ); + + const assignToUserTagsPayload = await assignToUserTagsResolver?.( + {}, + assignToUserTagsArgs, + assignToUserTagsContext, + ); + + expect(assignToUserTagsPayload?._id.toString()).toEqual( + testTag2?._id.toString(), + ); + + const tagAssignedToRandomUser2 = await TagUser.exists({ + tagId: testTag3, + userId: randomUser2?._id, + }); + + const tagAssignedToRandomUser3 = await TagUser.exists({ + tagId: testTag3, + userId: randomUser3?._id, + }); + + expect(tagAssignedToRandomUser2).toBeTruthy(); + expect(tagAssignedToRandomUser3).toBeTruthy(); + + // now remove them from that tag with the help of removeFromUserTags mutation + const args: MutationRemoveFromUserTagsArgs = { + input: { + selectedTagIds: [testTag3?._id.toString() ?? ""], + currentTagId: testTag2?._id.toString() ?? "", + }, + }; + + const context = { + userId: adminUser2?._id, + }; + + const { removeFromUserTags: removeFromUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/removeFromUserTags" + ); + + const payload = await removeFromUserTagsResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(testTag2?._id.toString()); + + const tagExistsForRandomUser2 = await TagUser.exists({ + tagId: testTag3, + userId: randomUser2?._id, + }); + + const tagExistsForRandomUser3 = await TagUser.exists({ + tagId: testTag3, + userId: randomUser3?._id, + }); + + expect(tagExistsForRandomUser2).toBeFalsy(); + expect(tagExistsForRandomUser3).toBeFalsy(); + }); + + it(`Should remove all the decendent tags too and returns the current tag`, async () => { + // create a new tag with the organization + const newTestTag = await OrganizationTagUser.create({ + name: "newTestTag", + organizationId: testOrg1?._id, + }); + + // assign this new test tag to random users + await Promise.all([ + User.findOneAndUpdate( + { + _id: randomUser2?._id, + }, + { + joinedOrganizations: testOrg1, + }, + ), + User.findOneAndUpdate( + { + _id: randomUser3?._id, + }, + { + joinedOrganizations: testOrg1, + }, + ), + TagUser.create({ + userId: randomUser2?._id, + tagId: newTestTag?._id, + organizationId: newTestTag?.organizationId, + }), + TagUser.create({ + userId: randomUser3?._id, + tagId: newTestTag?._id, + organizationId: newTestTag?.organizationId, + }), + ]); + + // now assign them a new sub tag, which will automatically assign them the parent tag also + const assignToUserTagsArgs: MutationAssignToUserTagsArgs = { + input: { + selectedTagIds: [testSubTag1?._id.toString() ?? ""], + currentTagId: newTestTag?._id.toString() ?? "", + }, + }; + const assignToUserTagsContext = { + userId: adminUser?._id, + }; + + const { assignToUserTags: assignToUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/assignToUserTags" + ); + + const assignToUserTagsPayload = await assignToUserTagsResolver?.( + {}, + assignToUserTagsArgs, + assignToUserTagsContext, + ); + + expect(assignToUserTagsPayload?._id.toString()).toEqual( + newTestTag?._id.toString(), + ); + + const subTagAssignedToRandomUser2 = await TagUser.exists({ + tagId: testSubTag1?._id, + userId: randomUser2?._id, + }); + + const subTagAssignedToRandomUser3 = await TagUser.exists({ + tagId: testSubTag1?._id, + userId: randomUser3?._id, + }); + + expect(subTagAssignedToRandomUser2).toBeTruthy(); + expect(subTagAssignedToRandomUser3).toBeTruthy(); + + const ancestorTagAssignedToRandomUser2 = await TagUser.exists({ + tagId: testTag?._id.toString() ?? "", + userId: randomUser2?._id, + }); + + const ancestorTagAssignedToRandomUser3 = await TagUser.exists({ + tagId: testTag?._id.toString() ?? "", + userId: randomUser3?._id, + }); + + expect(ancestorTagAssignedToRandomUser2).toBeTruthy(); + expect(ancestorTagAssignedToRandomUser3).toBeTruthy(); + + // now remove the parent tag, which will also remove the subtags + const args: MutationRemoveFromUserTagsArgs = { + input: { + selectedTagIds: [testTag?._id.toString() ?? ""], + currentTagId: newTestTag?._id.toString() ?? "", + }, + }; + const context = { + userId: adminUser?._id, + }; + + const { removeFromUserTags: removeFromUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/removeFromUserTags" + ); + + const payload = await removeFromUserTagsResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(newTestTag?._id.toString()); + + const subTagExistsForRandomUser2 = await TagUser.exists({ + tagId: testSubTag1?._id, + userId: randomUser2?._id, + }); + + const subTagExistsForRandomUser3 = await TagUser.exists({ + tagId: testSubTag1?._id, + userId: randomUser3?._id, + }); + + expect(subTagExistsForRandomUser2).toBeFalsy(); + expect(subTagExistsForRandomUser3).toBeFalsy(); + + const ancestorTagExistsForRandomUser2 = await TagUser.exists({ + tagId: testTag?._id.toString() ?? "", + userId: randomUser2?._id, + }); + + const ancestorTagExistsForRandomUser3 = await TagUser.exists({ + tagId: testTag?._id.toString() ?? "", + userId: randomUser3?._id, + }); + + expect(ancestorTagExistsForRandomUser2).toBeFalsy(); + expect(ancestorTagExistsForRandomUser3).toBeFalsy(); + }); +}); + +const testErrorScenario = async ({ + args, + context, + expectedError, +}: { + args: MutationRemoveFromUserTagsArgs; + context: { userId: string }; + expectedError: string; +}): Promise => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + const { removeFromUserTags: removeFromUserTagsResolver } = await import( + "../../../src/resolvers/Mutation/removeFromUserTags" + ); + await removeFromUserTagsResolver?.({}, args, context); + throw new Error("Expected error was not thrown"); + } catch (error: unknown) { + if (error instanceof Error) { + expect(error.message).toEqual(`Translated ${expectedError}`); + } else { + throw new Error("Unexpected error type"); + } + expect(spy).toHaveBeenLastCalledWith(expectedError); + } +}; diff --git a/tests/resolvers/Mutation/removeGroupChat.spec.ts b/tests/resolvers/Mutation/removeGroupChat.spec.ts deleted file mode 100644 index eb7bace2c83..00000000000 --- a/tests/resolvers/Mutation/removeGroupChat.spec.ts +++ /dev/null @@ -1,243 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { Organization, GroupChat, GroupChatMessage } from "../../../src/models"; -import type { MutationRemoveGroupChatArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ADMIN, -} from "../../../src/constants"; -import { - beforeAll, - afterAll, - describe, - it, - expect, - afterEach, - vi, -} from "vitest"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChatMessage } from "../../helpers/groupChat"; -import { cacheOrganizations } from "../../../src/services/OrganizationCache/cacheOrganizations"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testOrganization: TestOrganizationType; -let testGroupChat: TestGroupChatType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestGroupChatMessage(); - testUser = temp[0]; - testOrganization = temp[1]; - testGroupChat = temp[2]; - const testGroupChatMessage = temp[3]; - testGroupChat = await GroupChat.findOneAndUpdate( - { - _id: testGroupChat?._id, - }, - { - $push: { - messages: testGroupChatMessage?._id, - }, - }, - { - new: true, - }, - ); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> removeGroupChat", () => { - afterEach(() => { - vi.restoreAllMocks(); - vi.doUnmock("../../../src/constants"); - vi.resetModules(); - }); - - it(`throws NotFoundError if no groupChat exists with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementation((message) => `Translated ${message}`); - - try { - const args: MutationRemoveGroupChatArgs = { - chatId: new Types.ObjectId().toString(), - }; - - const context = { - userId: testUser?.id, - }; - const { removeGroupChat: removeGroupChatResolver } = await import( - "../../../src/resolvers/Mutation/removeGroupChat" - ); - - await removeGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${CHAT_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - - it(`throws NotFoundError if no organization exists with _id === groupChat.organization - for field organization of groupChat with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementation((message) => `Translated ${message}`); - - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: new Types.ObjectId().toString(), - }, - }, - ); - - const args: MutationRemoveGroupChatArgs = { - chatId: testGroupChat?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { removeGroupChat: removeGroupChatResolver } = await import( - "../../../src/resolvers/Mutation/removeGroupChat" - ); - - await removeGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${ORGANIZATION_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - - it(`throws UnauthorizedError if current user with _id === context.userId is - not an admin of organization with _id === groupChat.organization for groupChat - with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementation((message) => `Translated ${message}`); - - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: testOrganization?._id, - }, - }, - ); - - const updatedOrganization = await Organization.findOneAndUpdate( - { - _id: testOrganization?._id, - }, - { - $set: { - admins: [], - }, - }, - { - new: true, - }, - ); - - if (updatedOrganization !== null) { - await cacheOrganizations([updatedOrganization]); - } - - const args: MutationRemoveGroupChatArgs = { - chatId: testGroupChat?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { removeGroupChat: removeGroupChatResolver } = await import( - "../../../src/resolvers/Mutation/removeGroupChat" - ); - - await removeGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenCalledWith(USER_NOT_AUTHORIZED_ADMIN.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ADMIN.MESSAGE}`, - ); - } - }); - - it(`deletes the groupChat with _id === args.chatId and all groupChatMessages - associated to it and returns it`, async () => { - const updatedOrganization = await Organization.findOneAndUpdate( - { - _id: testOrganization?._id, - }, - { - $push: { - admins: testUser?._id, - }, - }, - { - new: true, - }, - ); - - if (updatedOrganization !== null) { - await cacheOrganizations([updatedOrganization]); - } - - const args: MutationRemoveGroupChatArgs = { - chatId: testGroupChat?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const { removeGroupChat: removeGroupChatResolver } = await import( - "../../../src/resolvers/Mutation/removeGroupChat" - ); - - const removeGroupChatPayload = await removeGroupChatResolver?.( - {}, - args, - context, - ); - - expect(removeGroupChatPayload).toEqual({ - ...testGroupChat?.toObject(), - updatedAt: expect.anything(), - }); - - const testDeletedGroupChatMessages = await GroupChatMessage.find({ - groupChatMessageBelongsTo: testGroupChat?._id, - }).lean(); - - expect(testDeletedGroupChatMessages).toEqual([]); - }); -}); diff --git a/tests/resolvers/Mutation/removeOrganization.spec.ts b/tests/resolvers/Mutation/removeOrganization.spec.ts index f2dfad353ca..1555db8e615 100644 --- a/tests/resolvers/Mutation/removeOrganization.spec.ts +++ b/tests/resolvers/Mutation/removeOrganization.spec.ts @@ -146,6 +146,7 @@ beforeAll(async () => { creator: testUsers[0]?._id, assignee: testUsers[1]?._id, assigner: testUsers[0]?._id, + assigneeType: "EventVolunteer", actionItemCategory: testCategory?._id, organization: testOrganization?._id, }); diff --git a/tests/resolvers/Mutation/removePost.spec.ts b/tests/resolvers/Mutation/removePost.spec.ts index 197e74b9c34..b16d32140f5 100644 --- a/tests/resolvers/Mutation/removePost.spec.ts +++ b/tests/resolvers/Mutation/removePost.spec.ts @@ -18,7 +18,7 @@ import { USER_NOT_AUTHORIZED_ERROR, USER_NOT_FOUND_ERROR, } from "../../../src/constants"; -import { AppUserProfile, Post } from "../../../src/models"; +import { AppUserProfile, File, Post } from "../../../src/models"; import type { TestPostType } from "../../helpers/posts"; import { createTestPost } from "../../helpers/posts"; import type { TestUserType } from "../../helpers/userAndOrg"; @@ -149,27 +149,53 @@ describe("resolvers -> Mutation -> removePost", () => { expect(removePostPayload).toEqual(testPost?.toObject()); }); - it(`deletes the post with image with _id === args.id and returns it`, async () => { + it(`deletes the post with file and returns it when referenceCount is 1`, async () => { const { requestContext } = await import("../../../src/libraries"); + const { BUCKET_NAME } = await import("../../../src/config/minio"); + vi.spyOn(requestContext, "translate").mockImplementationOnce( (message) => `Translated ${message}`, ); - const deletePreviousImage = await import( - "../../../src/utilities/encodedImageStorage/deletePreviousImage" - ); - const deleteImageSpy = vi - .spyOn(deletePreviousImage, "deletePreviousImage") - .mockImplementation(() => { - return Promise.resolve(); - }); + + // Mock Minio deleteFile with proper return type + const minioService = await import("../../../src/REST/services/minio"); + const minioDeleteSpy = vi + .spyOn(minioService, "deleteFile") + .mockImplementation(() => + Promise.resolve({ + $metadata: { + httpStatusCode: 204, + requestId: "mock-request-id", + attempts: 1, + totalRetryDelay: 0, + }, + }), + ); const [newTestUser, , newTestPost] = await createTestPost(); + const testFile = await File.create({ + _id: new Types.ObjectId(), + fileName: "test-file.jpg", + mimeType: "image/jpeg", + size: 1024, + hash: { + value: "test-hash-value", + algorithm: "SHA-256", + }, + uri: "test-uri", + referenceCount: 1, + status: "ACTIVE", + metadata: { + objectKey: "test-object-key", + }, + }); + const updatedPost = await Post.findOneAndUpdate( { _id: newTestPost?.id }, { $set: { - imageUrl: "images/fakeImagePathimage.png", + file: testFile._id, }, }, { new: true }, @@ -189,30 +215,62 @@ describe("resolvers -> Mutation -> removePost", () => { const removePostPayload = await removePostResolver?.({}, args, context); expect(removePostPayload).toEqual(updatedPost); - expect(deleteImageSpy).toBeCalledWith("images/fakeImagePathimage.png"); + + expect(minioDeleteSpy).toHaveBeenCalledWith( + BUCKET_NAME, + testFile.metadata.objectKey, + ); + + // Verify file was deleted from database + const deletedFile = await File.findById(testFile._id); + expect(deletedFile).toBeNull(); }); - it(`deletes the post with video with _id === args.id and returns it`, async () => { + it(`decrements file referenceCount when greater than 1`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementationOnce( (message) => `Translated ${message}`, ); - const deletePreviousVideo = await import( - "../../../src/utilities/encodedVideoStorage/deletePreviousVideo" - ); - const deleteVideoSpy = vi - .spyOn(deletePreviousVideo, "deletePreviousVideo") - .mockImplementation(() => { - return Promise.resolve(); - }); + + // Mock Minio deleteFile with proper return type + const minioService = await import("../../../src/REST/services/minio"); + const minioDeleteSpy = vi + .spyOn(minioService, "deleteFile") + .mockImplementation(() => + Promise.resolve({ + $metadata: { + httpStatusCode: 204, + requestId: "mock-request-id", + attempts: 1, + totalRetryDelay: 0, + }, + }), + ); const [newTestUser, , newTestPost] = await createTestPost(); + const testFile = await File.create({ + _id: new Types.ObjectId(), + fileName: "test-file.jpg", + mimeType: "image/jpeg", + size: 1024, + hash: { + value: "test-hash-value", + algorithm: "SHA-256", + }, + uri: "test-uri", + referenceCount: 2, + status: "ACTIVE", + metadata: { + objectKey: "test-object-key", + }, + }); + const updatedPost = await Post.findOneAndUpdate( { _id: newTestPost?.id }, { $set: { - videoUrl: "videos/fakeVideoPathvideo.png", + file: testFile._id, }, }, { new: true }, @@ -232,9 +290,16 @@ describe("resolvers -> Mutation -> removePost", () => { const removePostPayload = await removePostResolver?.({}, args, context); expect(removePostPayload).toEqual(updatedPost); - expect(deleteVideoSpy).toBeCalledWith("videos/fakeVideoPathvideo.png"); + + // Verify minioDeleteSpy was NOT called + expect(minioDeleteSpy).not.toHaveBeenCalled(); + + // Verify referenceCount was decremented + const updatedFile = await File.findById(testFile._id); + expect(updatedFile?.referenceCount).toBe(1); }); - it("throws an error if the user does not have appUserProfile", async () => { + + it("throws an error if the user does not have appUserProfile", async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementationOnce( (message) => `Translated ${message}`, diff --git a/tests/resolvers/Mutation/removeUserFromGroupChat.spec.ts b/tests/resolvers/Mutation/removeUserFromGroupChat.spec.ts deleted file mode 100644 index 20a18d76d1d..00000000000 --- a/tests/resolvers/Mutation/removeUserFromGroupChat.spec.ts +++ /dev/null @@ -1,228 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { User, Organization, GroupChat } from "../../../src/models"; -import type { MutationRemoveUserFromGroupChatArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { removeUserFromGroupChat as removeUserFromGroupChatResolver } from "../../../src/resolvers/Mutation/removeUserFromGroupChat"; -import { - CHAT_NOT_FOUND_ERROR, - ORGANIZATION_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, -} from "../../../src/constants"; -import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChatMessage } from "../../helpers/groupChat"; -import { deleteOrganizationFromCache } from "../../../src/services/OrganizationCache/deleteOrganizationFromCache"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testOrganization: TestOrganizationType; -let testGroupChat: TestGroupChatType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestGroupChatMessage(); - testUser = temp[0]; - testOrganization = temp[1]; - testGroupChat = temp[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> removeUserFromGroupChat", () => { - it(`throws NotFoundError if no groupChat exists with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - const args: MutationRemoveUserFromGroupChatArgs = { - chatId: new Types.ObjectId().toString(), - userId: "", - }; - - const context = { - userId: testUser?.id, - }; - - const { removeUserFromGroupChat: removeUserFromGroupChatResolver } = - await import("../../../src/resolvers/Mutation/removeUserFromGroupChat"); - - await removeUserFromGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - console.log((error as Error).message); - expect(spy).toBeCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws UnauthorizedError if current user with _id === context.userId is not - an admin of the organization of groupChat with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $set: { - organization: testOrganization?._id, - }, - }, - ); - - const args: MutationRemoveUserFromGroupChatArgs = { - chatId: testGroupChat?.id, - userId: "", - }; - - const context = { - userId: testUser?.id, - }; - - const { removeUserFromGroupChat: removeUserFromGroupChatResolver } = - await import("../../../src/resolvers/Mutation/removeUserFromGroupChat"); - - await removeUserFromGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - - it(`throws UnauthorizedError if users field of groupChat with _id === args.chatId - does not contain user with _id === args.userId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - await Organization.updateOne( - { - _id: testOrganization?._id, - }, - { - $push: { - admins: testUser?._id, - }, - }, - ); - - await User.updateOne( - { - _id: testUser?._id, - }, - { - $push: { - adminFor: testOrganization?._id, - }, - }, - ); - - const args: MutationRemoveUserFromGroupChatArgs = { - chatId: testGroupChat?.id, - userId: "", - }; - - const context = { - userId: testUser?.id, - }; - - const { removeUserFromGroupChat: removeUserFromGroupChatResolver } = - await import("../../../src/resolvers/Mutation/removeUserFromGroupChat"); - - await removeUserFromGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - - it(`removes user with _id === args.userId from users list field of groupChat - with _id === args.ChatId and returns the updated groupChat`, async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => `Translated ${message}`, - ); - - await GroupChat.updateOne( - { - _id: testGroupChat?._id, - }, - { - $push: { - users: testUser?._id, - }, - }, - ); - - const args: MutationRemoveUserFromGroupChatArgs = { - chatId: testGroupChat?.id, - userId: testUser?.id, - }; - - const context = { - userId: testUser?.id, - }; - - const removeUserFromGroupChatPayload = - await removeUserFromGroupChatResolver?.({}, args, context); - - const testRemoveUserFromGroupChatPayload = await GroupChat.findOne({ - _id: testGroupChat?._id, - }).lean(); - - expect(removeUserFromGroupChatPayload).toEqual( - testRemoveUserFromGroupChatPayload, - ); - }); - - it(`throws NotFoundError if no organization exists for groupChat with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - - const deletedOrgaization = await Organization.findOneAndDelete({ - _id: testOrganization?._id, - }); - if (deletedOrgaization) - await deleteOrganizationFromCache(deletedOrgaization); - - try { - const args: MutationRemoveUserFromGroupChatArgs = { - chatId: testGroupChat?.id, - userId: "", - }; - - const context = { - userId: testUser?.id, - }; - - const { removeUserFromGroupChat: removeUserFromGroupChatResolver } = - await import("../../../src/resolvers/Mutation/removeUserFromGroupChat"); - - await removeUserFromGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - ORGANIZATION_NOT_FOUND_ERROR.MESSAGE, - ); - } - }); -}); diff --git a/tests/resolvers/Mutation/removeUserTag.spec.ts b/tests/resolvers/Mutation/removeUserTag.spec.ts index cb9f4e815d5..7fd2375a9a8 100644 --- a/tests/resolvers/Mutation/removeUserTag.spec.ts +++ b/tests/resolvers/Mutation/removeUserTag.spec.ts @@ -49,14 +49,17 @@ beforeAll(async () => { { userId: testUser?._id, tagId: rootTag?._id, + organizationId: rootTag?.organizationId, }, { userId: testUser?._id, tagId: childTag1?._id, + organizationId: childTag1?.organizationId, }, { userId: testUser?._id, tagId: childTag2?._id, + organizationId: childTag2?.organizationId, }, ]); }); diff --git a/tests/resolvers/Mutation/sendMessageToDirectChat.spec.ts b/tests/resolvers/Mutation/sendMessageToChat.spec.ts similarity index 62% rename from tests/resolvers/Mutation/sendMessageToDirectChat.spec.ts rename to tests/resolvers/Mutation/sendMessageToChat.spec.ts index 3b589b02264..b661488ca65 100644 --- a/tests/resolvers/Mutation/sendMessageToDirectChat.spec.ts +++ b/tests/resolvers/Mutation/sendMessageToChat.spec.ts @@ -2,15 +2,12 @@ import "dotenv/config"; import type { Document } from "mongoose"; import type mongoose from "mongoose"; import { Types } from "mongoose"; -import type { - InterfaceDirectChat, - InterfaceDirectChatMessage, -} from "../../../src/models"; -import { User, Organization, DirectChat } from "../../../src/models"; -import type { MutationSendMessageToDirectChatArgs } from "../../../src/types/generatedGraphQLTypes"; +import type { InterfaceChat, InterfaceChatMessage } from "../../../src/models"; +import { User, Organization, Chat } from "../../../src/models"; +import type { MutationSendMessageToChatArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; -import { sendMessageToDirectChat as sendMessageToDirectChatResolver } from "../../../src/resolvers/Mutation/sendMessageToDirectChat"; +import { sendMessageToChat as sendMessageToChatResolver } from "../../../src/resolvers/Mutation/sendMessageToChat"; import { CHAT_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR, @@ -29,8 +26,7 @@ import type { TestUserType } from "../../helpers/userAndOrg"; let MONGOOSE_INSTANCE: typeof mongoose; let testUsers: TestUserType[]; -let testDirectChat: InterfaceDirectChat & - Document; +let testChat: InterfaceChat & Document; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); @@ -61,11 +57,14 @@ beforeAll(async () => { }, ); - testDirectChat = await DirectChat.create({ - title: "title", + testChat = await Chat.create({ + name: "Chat", creatorId: testUsers[0]?._id, organization: testOrganization._id, users: [testUsers[0]?._id, testUsers[1]?._id], + isGroup: false, + createdAt: "23456789", + updatedAt: "23456789", }); }); @@ -73,29 +72,30 @@ afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> Mutation -> sendMessageToDirectChat", () => { +describe("resolvers -> Mutation -> sendMessageToChat", () => { afterEach(async () => { vi.doUnmock("../../../src/constants"); vi.resetModules(); }); - it(`throws NotFoundError if no directChat exists with _id === args.chatId`, async () => { + it(`throws NotFoundError if no chat exists with _id === args.chatId`, async () => { const { requestContext } = await import("../../../src/libraries"); const spy = vi .spyOn(requestContext, "translate") .mockImplementationOnce((message) => message); try { - const args: MutationSendMessageToDirectChatArgs = { + const args: MutationSendMessageToChatArgs = { chatId: new Types.ObjectId().toString(), messageContent: "", }; const context = { userId: testUsers[0]?.id }; - const { sendMessageToDirectChat: sendMessageToDirectChatResolver } = - await import("../../../src/resolvers/Mutation/sendMessageToDirectChat"); + const { sendMessageToChat: sendMessageToChatResolver } = await import( + "../../../src/resolvers/Mutation/sendMessageToChat" + ); - await sendMessageToDirectChatResolver?.({}, args, context); + await sendMessageToChatResolver?.({}, args, context); } catch (error: unknown) { expect(spy).toBeCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); @@ -108,8 +108,8 @@ describe("resolvers -> Mutation -> sendMessageToDirectChat", () => { .spyOn(requestContext, "translate") .mockImplementationOnce((message) => message); try { - const args: MutationSendMessageToDirectChatArgs = { - chatId: testDirectChat.id, + const args: MutationSendMessageToChatArgs = { + chatId: testChat.id, messageContent: "", }; @@ -117,20 +117,21 @@ describe("resolvers -> Mutation -> sendMessageToDirectChat", () => { userId: new Types.ObjectId().toString(), }; - const { sendMessageToDirectChat: sendMessageToDirectChatResolver } = - await import("../../../src/resolvers/Mutation/sendMessageToDirectChat"); + const { sendMessageToChat: sendMessageToChatResolver } = await import( + "../../../src/resolvers/Mutation/sendMessageToChat" + ); - await sendMessageToDirectChatResolver?.({}, args, context); + await sendMessageToChatResolver?.({}, args, context); } catch (error: unknown) { expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); } }); - it(`creates the directChatMessage and returns it`, async () => { - await DirectChat.updateOne( + it(`creates the chatMessage and returns it`, async () => { + await Chat.updateOne( { - _id: testDirectChat._id, + _id: testChat._id, }, { $push: { @@ -139,20 +140,20 @@ describe("resolvers -> Mutation -> sendMessageToDirectChat", () => { }, ); - const args: MutationSendMessageToDirectChatArgs = { - chatId: testDirectChat.id, + const args: MutationSendMessageToChatArgs = { + chatId: testChat.id, messageContent: "messageContent", }; const pubsub = { publish: ( - _action: "MESSAGE_SENT_TO_DIRECT_CHAT", + _action: "MESSAGE_SENT_TO_CHAT", _payload: { - messageSentToDirectChat: InterfaceDirectChatMessage; + messageSentToChat: InterfaceChatMessage; }, ): { _action: string; - _payload: { messageSentToDirectChat: InterfaceDirectChatMessage }; + _payload: { messageSentToChat: InterfaceChatMessage }; } => { return { _action, _payload }; }, @@ -163,14 +164,16 @@ describe("resolvers -> Mutation -> sendMessageToDirectChat", () => { pubsub, }; - const sendMessageToDirectChatPayload = - await sendMessageToDirectChatResolver?.({}, args, context); + const sendMessageToChatPayload = await sendMessageToChatResolver?.( + {}, + args, + context, + ); - expect(sendMessageToDirectChatPayload).toEqual( + expect(sendMessageToChatPayload).toEqual( expect.objectContaining({ - directChatMessageBelongsTo: testDirectChat._id, + chatMessageBelongsTo: testChat._id, sender: testUsers[0]?._id, - receiver: testUsers[1]?._id, messageContent: "messageContent", }), ); diff --git a/tests/resolvers/Mutation/sendMessageToGroupChat.spec.ts b/tests/resolvers/Mutation/sendMessageToGroupChat.spec.ts deleted file mode 100644 index b9d0ee8c1ae..00000000000 --- a/tests/resolvers/Mutation/sendMessageToGroupChat.spec.ts +++ /dev/null @@ -1,170 +0,0 @@ -import "dotenv/config"; -import type { Document } from "mongoose"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import type { - InterfaceGroupChat, - InterfaceGroupChatMessage, -} from "../../../src/models"; -import { GroupChat } from "../../../src/models"; -import type { MutationSendMessageToGroupChatArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; - -import { sendMessageToGroupChat as sendMessageToGroupChatResolver } from "../../../src/resolvers/Mutation/sendMessageToGroupChat"; -import { - CHAT_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../../src/constants"; -import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import { createTestUserAndOrganization } from "../../helpers/userAndOrg"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testGroupChat: InterfaceGroupChat & - Document; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestUserAndOrganization(); - testUser = temp[0]; - - const testOrganization = temp[1]; - - testGroupChat = await GroupChat.create({ - title: "title", - creatorId: testUser?._id, - organization: testOrganization?._id, - }); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> sendMessageToGroupChat", () => { - it(`throws NotFoundError if no groupChat exists with _id === args.chatId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - const args: MutationSendMessageToGroupChatArgs = { - chatId: new Types.ObjectId().toString(), - messageContent: "", - }; - - const context = { userId: testUser?.id }; - - const { sendMessageToGroupChat: sendMessageToGroupChatResolver } = - await import("../../../src/resolvers/Mutation/sendMessageToGroupChat"); - - await sendMessageToGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(CHAT_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws NotFoundError current user with _id === context.userId does not exist`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - const args: MutationSendMessageToGroupChatArgs = { - chatId: testGroupChat.id, - messageContent: "", - }; - - const context = { - userId: new Types.ObjectId().toString(), - }; - - const { sendMessageToGroupChat: sendMessageToGroupChatResolver } = - await import("../../../src/resolvers/Mutation/sendMessageToGroupChat"); - - await sendMessageToGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it(`throws UnauthorizedError if users field of groupChat with _id === args.chatId - does not contain current user with _id === context.userId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => message); - try { - const args: MutationSendMessageToGroupChatArgs = { - chatId: testGroupChat.id, - messageContent: "", - }; - - const context = { - userId: testUser?.id, - }; - - const { sendMessageToGroupChat: sendMessageToGroupChatResolver } = - await import("../../../src/resolvers/Mutation/sendMessageToGroupChat"); - - await sendMessageToGroupChatResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - - it(`creates the groupChatMessage and returns it`, async () => { - await GroupChat.updateOne( - { - _id: testGroupChat._id, - }, - { - $push: { - users: testUser?._id, - }, - }, - ); - - const args: MutationSendMessageToGroupChatArgs = { - chatId: testGroupChat.id, - messageContent: "messageContent", - }; - - const pubsub = { - publish: ( - _action: "MESSAGE_SENT_TO_GROUP_CHAT", - _payload: { - messageSentToGroupChat: InterfaceGroupChatMessage; - }, - ): { - _action: string; - _payload: { messageSentToGroupChat: InterfaceGroupChatMessage }; - } => { - return { _action, _payload }; - }, - }; - - const context = { - userId: testUser?.id, - pubsub, - }; - - const sendMessageToGroupChatPayload = - await sendMessageToGroupChatResolver?.({}, args, context); - - expect(sendMessageToGroupChatPayload).toEqual( - expect.objectContaining({ - groupChatMessageBelongsTo: testGroupChat._id, - sender: testUser?._id, - messageContent: "messageContent", - }), - ); - }); -}); diff --git a/tests/resolvers/Mutation/unassignUserTag.spec.ts b/tests/resolvers/Mutation/unassignUserTag.spec.ts index 16ce061c8f4..de0a4fd84df 100644 --- a/tests/resolvers/Mutation/unassignUserTag.spec.ts +++ b/tests/resolvers/Mutation/unassignUserTag.spec.ts @@ -228,6 +228,7 @@ describe("resolvers -> Mutation -> unassignUserTag", () => { // Assign the tag to the user await TagUser.create({ ...args.input, + organizationId: testTag?.organizationId, }); // Test the unassignUserTag resolver @@ -266,11 +267,13 @@ describe("resolvers -> Mutation -> unassignUserTag", () => { // Assign the parent and sub tag to the user await TagUser.create({ ...args.input, + organizationId: testTag?.organizationId, }); await TagUser.create({ ...args.input, tagId: testSubTag1 ? testSubTag1._id.toString() : "", + organizationId: testSubTag1?.organizationId, }); // Test the unassignUserTag resolver diff --git a/tests/resolvers/Mutation/updateActionItem.spec.ts b/tests/resolvers/Mutation/updateActionItem.spec.ts index b7406cd6fa1..fc6c8eb5cd8 100644 --- a/tests/resolvers/Mutation/updateActionItem.spec.ts +++ b/tests/resolvers/Mutation/updateActionItem.spec.ts @@ -5,9 +5,10 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { ACTION_ITEM_NOT_FOUND_ERROR, EVENT_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, USER_NOT_FOUND_ERROR, - USER_NOT_MEMBER_FOR_ORGANIZATION, } from "../../../src/constants"; import { updateActionItem as updateActionItemResolver } from "../../../src/resolvers/Mutation/updateActionItem"; import type { MutationUpdateActionItemArgs } from "../../../src/types/generatedGraphQLTypes"; @@ -16,26 +17,28 @@ import type { TestOrganizationType, TestUserType, } from "../../helpers/userAndOrg"; -import { - createTestUser, - createTestUserAndOrganization, -} from "../../helpers/userAndOrg"; +import { createTestUserAndOrganization } from "../../helpers/userAndOrg"; import { nanoid } from "nanoid"; -import { ActionItem, AppUserProfile, Event, User } from "../../../src/models"; +import { ActionItem, AppUserProfile, Event } from "../../../src/models"; import type { TestActionItemType } from "../../helpers/actionItem"; import { createTestActionItem } from "../../helpers/actionItem"; import type { TestActionItemCategoryType } from "../../helpers/actionItemCategory"; import type { TestEventType } from "../../helpers/events"; +import type { + TestVolunteerGroupType, + TestVolunteerType, +} from "../../helpers/volunteers"; +import { createTestVolunteerAndGroup } from "../../helpers/volunteers"; -let randomUser: TestUserType; -let assignedTestUser: TestUserType; let testUser: TestUserType; let testUser2: TestUserType; let testOrganization: TestOrganizationType; let testCategory: TestActionItemCategoryType; let testActionItem: TestActionItemType; let testEvent: TestEventType; +let tVolunteer: TestVolunteerType; +let tVolunteerGroup: TestVolunteerGroupType; let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { @@ -45,10 +48,8 @@ beforeAll(async () => { (message) => message, ); - randomUser = await createTestUser(); - [testUser2] = await createTestUserAndOrganization(); - [testUser, testOrganization, testCategory, testActionItem, assignedTestUser] = + [testUser, testOrganization, testCategory, testActionItem] = await createTestActionItem(); testEvent = await Event.create({ @@ -63,6 +64,8 @@ beforeAll(async () => { admins: [testUser2?._id], organization: testOrganization?._id, }); + + [, , , tVolunteer, tVolunteerGroup] = await createTestVolunteerAndGroup(); }); afterAll(async () => { @@ -75,7 +78,7 @@ describe("resolvers -> Mutation -> updateActionItem", () => { const args: MutationUpdateActionItemArgs = { id: new Types.ObjectId().toString(), data: { - assigneeId: randomUser?._id, + assigneeId: tVolunteer?._id, }, }; @@ -94,7 +97,7 @@ describe("resolvers -> Mutation -> updateActionItem", () => { const args: MutationUpdateActionItemArgs = { id: new Types.ObjectId().toString(), data: { - assigneeId: randomUser?._id, + assigneeId: tVolunteer?._id, }, }; @@ -116,6 +119,7 @@ describe("resolvers -> Mutation -> updateActionItem", () => { id: testActionItem?._id, data: { assigneeId: new Types.ObjectId().toString(), + assigneeType: "EventVolunteer", }, }; @@ -125,16 +129,31 @@ describe("resolvers -> Mutation -> updateActionItem", () => { await updateActionItemResolver?.({}, args, context); } catch (error: unknown) { - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + expect((error as Error).message).toEqual( + EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE, + ); } }); - it(`throws NotFoundError if the new asignee is not a member of the organization`, async () => { + it(`throws NotFoundError if no user exists with _id === args.data.assigneeId`, async () => { try { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteerGroup", + assigneeGroup: new Types.ObjectId().toString(), + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + }); + const args: MutationUpdateActionItemArgs = { - id: testActionItem?._id, + id: testActionItem2?._id.toString() ?? "", data: { - assigneeId: randomUser?._id, + assigneeId: new Types.ObjectId().toString(), + assigneeType: "EventVolunteerGroup", }, }; @@ -145,17 +164,83 @@ describe("resolvers -> Mutation -> updateActionItem", () => { await updateActionItemResolver?.({}, args, context); } catch (error: unknown) { expect((error as Error).message).toEqual( - USER_NOT_MEMBER_FOR_ORGANIZATION.MESSAGE, + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE, ); } }); + it(`throws NotFoundError if no user exists with _id === args.data.assigneeId`, async () => { + try { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteerGroup", + assigneeGroup: new Types.ObjectId().toString(), + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + }); + + const args: MutationUpdateActionItemArgs = { + id: testActionItem2?._id.toString() ?? "", + data: { + assigneeId: new Types.ObjectId().toString(), + assigneeType: "EventVolunteerGroup", + }, + }; + + const context = { + userId: testUser?._id, + }; + + await updateActionItemResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE, + ); + } + }); + it(`throws NotFoundError if no user exists when assigneeUser (doesn't exist)`, async () => { + try { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "User", + assigneeUser: new Types.ObjectId().toString(), + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: null, + }); + + const args: MutationUpdateActionItemArgs = { + id: testActionItem2?._id.toString() ?? "", + data: { + assigneeId: new Types.ObjectId().toString(), + assigneeType: "User", + }, + }; + + const context = { + userId: testUser?._id, + }; + + await updateActionItemResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + it(`throws NotAuthorizedError if the user is not a superadmin/orgAdmin/eventAdmin`, async () => { try { const args: MutationUpdateActionItemArgs = { id: testActionItem?._id, data: { - assigneeId: testUser?._id, + assigneeId: tVolunteer?._id, + assigneeType: "EventVolunteer", }, }; @@ -171,18 +256,280 @@ describe("resolvers -> Mutation -> updateActionItem", () => { } }); - it(`updates the action item and returns it as an admin`, async () => { + it(`throws NotAuthorizedError if the actionItem.event doesn't exist`, async () => { + try { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + event: new Types.ObjectId().toString(), + creator: testUser?._id, + assigneeType: "EventVolunteer", + assignee: new Types.ObjectId().toString(), + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + }); + + const args: MutationUpdateActionItemArgs = { + id: testActionItem2?._id.toString() ?? "", + data: { + assigneeId: tVolunteer?._id, + assigneeType: "EventVolunteer", + }, + }; + + const context = { + userId: testUser2?._id, + }; + + await updateActionItemResolver?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual(EVENT_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`updates the action item and sets action item as completed`, async () => { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteer", + assignee: tVolunteer?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + allottedHours: 2, + isCompleted: false, + }); + + const testActionItem3 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteer", + assignee: tVolunteer?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + allottedHours: 0, + isCompleted: false, + }); + const args: MutationUpdateActionItemArgs = { - id: testActionItem?._id, + id: testActionItem2?._id.toString() ?? "", + data: { + assigneeId: tVolunteer?._id, + assigneeType: "EventVolunteer", + isCompleted: true, + }, + }; + + const args2: MutationUpdateActionItemArgs = { + id: testActionItem3?._id.toString() ?? "", + data: { + assigneeId: tVolunteer?._id, + assigneeType: "EventVolunteer", + isCompleted: true, + }, + }; + + const context = { + userId: testUser?._id, + }; + + await updateActionItemResolver?.({}, args, context); + await updateActionItemResolver?.({}, args2, context); + }); + + it(`updates the action item and sets action item as not completed`, async () => { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteer", + assignee: tVolunteer?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + allottedHours: 2, + isCompleted: true, + }); + + const testActionItem3 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteer", + assignee: tVolunteer?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + isCompleted: true, + }); + + const args: MutationUpdateActionItemArgs = { + id: testActionItem2?._id.toString() ?? "", + data: { + assigneeId: tVolunteer?._id, + assigneeType: "EventVolunteer", + isCompleted: false, + }, + }; + + const args2: MutationUpdateActionItemArgs = { + id: testActionItem3?._id.toString() ?? "", + data: { + assigneeId: tVolunteer?._id, + assigneeType: "EventVolunteer", + isCompleted: false, + }, + }; + + const context = { + userId: testUser?._id, + }; + + await updateActionItemResolver?.({}, args, context); + await updateActionItemResolver?.({}, args2, context); + }); + + it(`updates the action item and sets action item as completed (Volunteer Group)`, async () => { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteerGroup", + assigneeGroup: tVolunteerGroup?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + allottedHours: 2, + isCompleted: false, + }); + + const testActionItem3 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteerGroup", + assigneeGroup: tVolunteerGroup?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + allottedHours: 0, + isCompleted: false, + }); + + const args: MutationUpdateActionItemArgs = { + id: testActionItem2?._id.toString() ?? "", + data: { + assigneeId: tVolunteerGroup?._id, + assigneeType: "EventVolunteerGroup", + isCompleted: true, + }, + }; + + const args2: MutationUpdateActionItemArgs = { + id: testActionItem3?._id.toString() ?? "", data: { - assigneeId: assignedTestUser?._id, + assigneeId: tVolunteerGroup?._id, + assigneeType: "EventVolunteerGroup", + isCompleted: true, }, }; - // console.log(testUser?._id); + const context = { userId: testUser?._id, }; + await updateActionItemResolver?.({}, args, context); + await updateActionItemResolver?.({}, args2, context); + }); + + it(`updates the action item and sets action item as not completed (Volunteer Group)`, async () => { + const testActionItem2 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteerGroup", + assigneeGroup: tVolunteerGroup?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + allottedHours: 2, + isCompleted: true, + }); + + const testActionItem3 = await ActionItem.create({ + title: `title${nanoid().toLowerCase()}`, + description: `description${nanoid().toLowerCase()}`, + creator: testUser?._id, + assigneeType: "EventVolunteerGroup", + assigneeGroup: tVolunteerGroup?._id, + organization: testOrganization?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + event: testEvent?._id, + isCompleted: true, + }); + + const args: MutationUpdateActionItemArgs = { + id: testActionItem2?._id.toString() ?? "", + data: { + assigneeId: tVolunteerGroup?._id, + assigneeType: "EventVolunteerGroup", + isCompleted: false, + }, + }; + + const args2: MutationUpdateActionItemArgs = { + id: testActionItem3?._id.toString() ?? "", + data: { + assigneeId: tVolunteerGroup?._id, + assigneeType: "EventVolunteerGroup", + isCompleted: false, + }, + }; + + const context = { + userId: testUser?._id, + }; + + await updateActionItemResolver?.({}, args, context); + await updateActionItemResolver?.({}, args2, context); + }); + + it(`updates the actionItem when the user is authorized as an eventAdmin`, async () => { + const updatedTestActionItem = await ActionItem.findOneAndUpdate( + { + _id: testActionItem?._id, + }, + { + event: testEvent?._id, + }, + { + new: true, + }, + ); + + const args: MutationUpdateActionItemArgs = { + data: { + isCompleted: true, + }, + id: updatedTestActionItem?._id.toString() ?? "", + }; + + const context = { + userId: testUser2?._id, + }; + const updatedActionItemPayload = await updateActionItemResolver?.( {}, args, @@ -191,19 +538,19 @@ describe("resolvers -> Mutation -> updateActionItem", () => { expect(updatedActionItemPayload).toEqual( expect.objectContaining({ - assignee: assignedTestUser?._id, actionItemCategory: testCategory?._id, + isCompleted: true, }), ); }); - it(`updates the action item and returns it as superadmin`, async () => { - const superAdminTestUser = await AppUserProfile.findOneAndUpdate( + it(`updates the actionItem isCompleted is undefined (EventVolunteer)`, async () => { + const updatedTestActionItem = await ActionItem.findOneAndUpdate( { - userId: randomUser?._id, + _id: testActionItem?._id, }, { - isSuperAdmin: true, + event: testEvent?._id, }, { new: true, @@ -211,14 +558,16 @@ describe("resolvers -> Mutation -> updateActionItem", () => { ); const args: MutationUpdateActionItemArgs = { - id: testActionItem?._id, data: { - assigneeId: testUser?._id, + isCompleted: undefined, + assigneeId: undefined, + assigneeType: "EventVolunteer", }, + id: updatedTestActionItem?._id.toString() ?? "", }; const context = { - userId: superAdminTestUser?.userId, + userId: testUser2?._id, }; const updatedActionItemPayload = await updateActionItemResolver?.( @@ -229,53 +578,53 @@ describe("resolvers -> Mutation -> updateActionItem", () => { expect(updatedActionItemPayload).toEqual( expect.objectContaining({ - assignee: testUser?._id, actionItemCategory: testCategory?._id, + isCompleted: true, }), ); }); - it(`throws NotFoundError if no event exists to which the action item is associated`, async () => { + it(`updates the actionItem isCompleted is undefined (EventVolunteerGroup)`, async () => { const updatedTestActionItem = await ActionItem.findOneAndUpdate( { _id: testActionItem?._id, }, { - event: new Types.ObjectId().toString(), + event: testEvent?._id, }, { new: true, }, ); - await User.updateOne( - { - _id: randomUser?._id, - }, - { - $push: { joinedOrganizations: testOrganization?._id }, + const args: MutationUpdateActionItemArgs = { + data: { + isCompleted: undefined, + assigneeId: undefined, + assigneeType: "EventVolunteerGroup", }, - ); + id: updatedTestActionItem?._id.toString() ?? "", + }; - try { - const args: MutationUpdateActionItemArgs = { - id: updatedTestActionItem?._id.toString() ?? "", - data: { - assigneeId: randomUser?._id, - }, - }; + const context = { + userId: testUser2?._id, + }; - const context = { - userId: testUser?._id, - }; + const updatedActionItemPayload = await updateActionItemResolver?.( + {}, + args, + context, + ); - await updateActionItemResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(EVENT_NOT_FOUND_ERROR.MESSAGE); - } + expect(updatedActionItemPayload).toEqual( + expect.objectContaining({ + actionItemCategory: testCategory?._id, + isCompleted: true, + }), + ); }); - it(`updates the actionItem when the user is authorized as an eventAdmin`, async () => { + it(`updates the actionItem isCompleted is undefined (User)`, async () => { const updatedTestActionItem = await ActionItem.findOneAndUpdate( { _id: testActionItem?._id, @@ -290,7 +639,9 @@ describe("resolvers -> Mutation -> updateActionItem", () => { const args: MutationUpdateActionItemArgs = { data: { - isCompleted: true, + isCompleted: undefined, + assigneeId: undefined, + assigneeType: "User", }, id: updatedTestActionItem?._id.toString() ?? "", }; @@ -312,6 +663,7 @@ describe("resolvers -> Mutation -> updateActionItem", () => { }), ); }); + it("throws error if user does not have appUserProfile", async () => { await AppUserProfile.deleteOne({ userId: testUser2?._id, diff --git a/tests/resolvers/Mutation/updateEventVolunteer.spec.ts b/tests/resolvers/Mutation/updateEventVolunteer.spec.ts index 4a749cca91e..86ab7b15217 100644 --- a/tests/resolvers/Mutation/updateEventVolunteer.spec.ts +++ b/tests/resolvers/Mutation/updateEventVolunteer.spec.ts @@ -1,38 +1,29 @@ import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import type { MutationUpdateEventVolunteerArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; -import { - USER_NOT_FOUND_ERROR, - EventVolunteerResponse, - EVENT_VOLUNTEER_NOT_FOUND_ERROR, - EVENT_VOLUNTEER_INVITE_USER_MISTMATCH, -} from "../../../src/constants"; -import { - beforeAll, - afterAll, - describe, - it, - expect, - vi, - afterEach, -} from "vitest"; -import type { - TestEventType, - TestEventVolunteerType, -} from "../../helpers/events"; -import { createTestEventAndVolunteer } from "../../helpers/events"; -import { createTestUser } from "../../helpers/user"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { TestEventVolunteerType } from "../../helpers/events"; +import type { TestUserType } from "../../helpers/user"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import type { InterfaceEventVolunteer } from "../../../src/models"; +import { updateEventVolunteer } from "../../../src/resolvers/Mutation/updateEventVolunteer"; +import { EVENT_VOLUNTEER_INVITE_USER_MISTMATCH } from "../../../src/constants"; +import { requestContext } from "../../../src/libraries"; let MONGOOSE_INSTANCE: typeof mongoose; -let testEvent: TestEventType; -let testEventVolunteer: TestEventVolunteerType; +let testUser1: TestUserType; +let testUser2: TestUserType; +let testEventVolunteer1: TestEventVolunteerType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const temp = await createTestEventAndVolunteer(); - testEvent = temp[2]; - testEventVolunteer = temp[3]; + vi.spyOn(requestContext, "translate").mockImplementation( + (message) => message, + ); + const [, , user1, user2, volunteer1] = await createVolunteerAndActions(); + + testUser1 = user1; + testUser2 = user2; + testEventVolunteer1 = volunteer1; }); afterAll(async () => { @@ -40,161 +31,55 @@ afterAll(async () => { }); describe("resolvers -> Mutation -> updateEventVolunteer", () => { - afterEach(() => { - vi.doUnmock("../../../src/constants"); - vi.resetModules(); - }); - it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - + it(`throws error if context.userId !== volunteer._id`, async () => { try { - const args: MutationUpdateEventVolunteerArgs = { - id: testEventVolunteer?._id, - data: { - response: EventVolunteerResponse.YES, + (await updateEventVolunteer?.( + {}, + { + id: testEventVolunteer1?._id, + data: { + isPublic: false, + }, }, - }; - - const context = { userId: new Types.ObjectId().toString() }; - - const { updateEventVolunteer: updateEventVolunteerResolver } = - await import("../../../src/resolvers/Mutation/updateEventVolunteer"); - - await updateEventVolunteerResolver?.({}, args, context); + { userId: testUser2?._id.toString() }, + )) as unknown as InterfaceEventVolunteer[]; } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - - it(`throws NotFoundError if no event volunteer exists with _id === args.id`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const args: MutationUpdateEventVolunteerArgs = { - id: new Types.ObjectId().toString(), - data: { - response: EventVolunteerResponse.YES, - }, - }; - - const context = { userId: testEventVolunteer?.userId }; - - const { updateEventVolunteer: updateEventVolunteerResolver } = - await import("../../../src/resolvers/Mutation/updateEventVolunteer"); - - await updateEventVolunteerResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith( - EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE, - ); - expect((error as Error).message).toEqual( - `Translated ${EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE}`, - ); - } - }); - - it(`throws ConflictError if userId of volunteer is not equal to context.userId `, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const args: MutationUpdateEventVolunteerArgs = { - id: testEventVolunteer?._id, - data: { - response: EventVolunteerResponse.YES, - }, - }; - - const testUser2 = await createTestUser(); - const context = { userId: testUser2?._id }; - const { updateEventVolunteer: updateEventVolunteerResolver } = - await import("../../../src/resolvers/Mutation/updateEventVolunteer"); - - await updateEventVolunteerResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith( EVENT_VOLUNTEER_INVITE_USER_MISTMATCH.MESSAGE, ); - expect((error as Error).message).toEqual( - `Translated ${EVENT_VOLUNTEER_INVITE_USER_MISTMATCH.MESSAGE}`, - ); } }); - it(`updates the Event Volunteer with _id === args.id and returns it`, async () => { - const args: MutationUpdateEventVolunteerArgs = { - id: testEventVolunteer?._id, - data: { - isAssigned: true, - response: EventVolunteerResponse.YES, - isInvited: true, - eventId: testEvent?._id, - }, - }; - - const context = { userId: testEventVolunteer?.userId }; - - const { updateEventVolunteer: updateEventVolunteerResolver } = await import( - "../../../src/resolvers/Mutation/updateEventVolunteer" - ); - - const updatedEventVolunteer = await updateEventVolunteerResolver?.( + it(`data remains same if no values are updated`, async () => { + const updatedVolunteer = (await updateEventVolunteer?.( {}, - args, - context, - ); - - expect(updatedEventVolunteer).toEqual( - expect.objectContaining({ - isAssigned: true, - response: EventVolunteerResponse.YES, - eventId: testEvent?._id, - isInvited: true, - }), + { + id: testEventVolunteer1?._id, + data: {}, + }, + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceEventVolunteer; + expect(updatedVolunteer.isPublic).toEqual(testEventVolunteer1?.isPublic); + expect(updatedVolunteer.hasAccepted).toEqual( + testEventVolunteer1?.hasAccepted, ); }); - it(`updates the Event Volunteer with _id === args.id, even if args.data is empty object`, async () => { - const t = await createTestEventAndVolunteer(); - testEventVolunteer = t[3]; - const args: MutationUpdateEventVolunteerArgs = { - id: testEventVolunteer?._id, - data: {}, - }; - - const context = { userId: testEventVolunteer?.userId }; - - const { updateEventVolunteer: updateEventVolunteerResolver } = await import( - "../../../src/resolvers/Mutation/updateEventVolunteer" - ); - - const updatedEventVolunteer = await updateEventVolunteerResolver?.( + it(`updates EventVolunteer`, async () => { + const updatedVolunteer = (await updateEventVolunteer?.( {}, - args, - context, - ); - - expect(updatedEventVolunteer).toEqual( - expect.objectContaining({ - isAssigned: testEventVolunteer?.isAssigned, - response: testEventVolunteer?.response, - eventId: testEventVolunteer?.eventId, - isInvited: testEventVolunteer?.isInvited, - }), - ); + { + id: testEventVolunteer1?._id, + data: { + isPublic: false, + hasAccepted: false, + assignments: [], + }, + }, + { userId: testUser1?._id.toString() }, + )) as unknown as InterfaceEventVolunteer; + expect(updatedVolunteer.isPublic).toEqual(false); + expect(updatedVolunteer.hasAccepted).toEqual(false); + expect(updatedVolunteer.assignments).toEqual([]); }); }); diff --git a/tests/resolvers/Mutation/updateEventVolunteerGroup.spec.ts b/tests/resolvers/Mutation/updateEventVolunteerGroup.spec.ts index 645161bec65..c735688e634 100644 --- a/tests/resolvers/Mutation/updateEventVolunteerGroup.spec.ts +++ b/tests/resolvers/Mutation/updateEventVolunteerGroup.spec.ts @@ -1,14 +1,12 @@ import type mongoose from "mongoose"; import { Types } from "mongoose"; -import type { - MutationUpdateEventVolunteerArgs, - MutationUpdateEventVolunteerGroupArgs, -} from "../../../src/types/generatedGraphQLTypes"; +import type { MutationUpdateEventVolunteerGroupArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; import { USER_NOT_FOUND_ERROR, EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, + EVENT_NOT_FOUND_ERROR, } from "../../../src/constants"; import { beforeAll, @@ -25,6 +23,8 @@ import { createTestUser } from "../../helpers/user"; import type { TestUserType } from "../../helpers/userAndOrg"; import type { TestEventVolunteerGroupType } from "./createEventVolunteer.spec"; import { EventVolunteerGroup } from "../../../src/models"; +import { requestContext } from "../../../src/libraries"; +import { updateEventVolunteerGroup } from "../../../src/resolvers/Mutation/updateEventVolunteerGroup"; let MONGOOSE_INSTANCE: typeof mongoose; let testEvent: TestEventType; @@ -35,9 +35,9 @@ beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); [eventAdminUser, , testEvent] = await createTestEvent(); testGroup = await EventVolunteerGroup.create({ - creatorId: eventAdminUser?._id, - eventId: testEvent?._id, - leaderId: eventAdminUser?._id, + creator: eventAdminUser?._id, + event: testEvent?._id, + leader: eventAdminUser?._id, name: "Test group", volunteersRequired: 2, }); @@ -54,8 +54,6 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { }); it(`throws NotFoundError if no user exists with _id === context.userId `, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi .spyOn(requestContext, "translate") .mockImplementationOnce((message) => `Translated ${message}`); @@ -65,17 +63,12 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { id: testGroup?._id, data: { name: "updated name", + eventId: testEvent?._id, }, }; const context = { userId: new Types.ObjectId().toString() }; - - const { updateEventVolunteerGroup: updateEventVolunteerGroupResolver } = - await import( - "../../../src/resolvers/Mutation/updateEventVolunteerGroup" - ); - - await updateEventVolunteerGroupResolver?.({}, args, context); + await updateEventVolunteerGroup?.({}, args, context); } catch (error: unknown) { expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); expect((error as Error).message).toEqual( @@ -85,8 +78,6 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { }); it(`throws NotFoundError if no event volunteer group exists with _id === args.id`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi .spyOn(requestContext, "translate") .mockImplementationOnce((message) => `Translated ${message}`); @@ -96,17 +87,12 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { id: new Types.ObjectId().toString(), data: { name: "updated name", + eventId: testEvent?._id, }, }; const context = { userId: eventAdminUser?._id }; - - const { updateEventVolunteerGroup: updateEventVolunteerGroupResolver } = - await import( - "../../../src/resolvers/Mutation/updateEventVolunteerGroup" - ); - - await updateEventVolunteerGroupResolver?.({}, args, context); + await updateEventVolunteerGroup?.({}, args, context); } catch (error: unknown) { expect(spy).toHaveBeenLastCalledWith( EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE, @@ -117,9 +103,31 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { } }); - it(`throws UnauthorizedError if current user is not leader of group `, async () => { - const { requestContext } = await import("../../../src/libraries"); + it(`throws NotFoundError if no event exists with _id === args.data.eventId`, async () => { + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + try { + const args: MutationUpdateEventVolunteerGroupArgs = { + id: testGroup?._id, + data: { + name: "updated name", + eventId: new Types.ObjectId().toString(), + }, + }; + + const context = { userId: eventAdminUser?._id }; + await updateEventVolunteerGroup?.({}, args, context); + } catch (error: unknown) { + expect(spy).toHaveBeenLastCalledWith(EVENT_NOT_FOUND_ERROR.MESSAGE); + expect((error as Error).message).toEqual( + `Translated ${EVENT_NOT_FOUND_ERROR.MESSAGE}`, + ); + } + }); + + it(`throws UnauthorizedError if current user is not leader of group `, async () => { const spy = vi .spyOn(requestContext, "translate") .mockImplementationOnce((message) => `Translated ${message}`); @@ -129,18 +137,14 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { id: testGroup?._id, data: { name: "updated name", + eventId: testEvent?._id, }, }; const testUser2 = await createTestUser(); const context = { userId: testUser2?._id }; - const { updateEventVolunteerGroup: updateEventVolunteerGroupResolver } = - await import( - "../../../src/resolvers/Mutation/updateEventVolunteerGroup" - ); - - await updateEventVolunteerGroupResolver?.({}, args, context); + await updateEventVolunteerGroup?.({}, args, context); } catch (error: unknown) { expect(spy).toHaveBeenLastCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); expect((error as Error).message).toEqual( @@ -160,20 +164,12 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { }; const context = { userId: eventAdminUser?._id }; - - const { updateEventVolunteerGroup: updateEventVolunteerGroupResolver } = - await import("../../../src/resolvers/Mutation/updateEventVolunteerGroup"); - - const updatedGroup = await updateEventVolunteerGroupResolver?.( - {}, - args, - context, - ); + const updatedGroup = await updateEventVolunteerGroup?.({}, args, context); expect(updatedGroup).toEqual( expect.objectContaining({ name: "updated", - eventId: testEvent?._id, + event: testEvent?._id, volunteersRequired: 10, }), ); @@ -182,35 +178,26 @@ describe("resolvers -> Mutation -> updateEventVolunteerGroup", () => { it(`updates the Event Volunteer group with _id === args.id, even if args.data is empty object`, async () => { const testGroup2 = await EventVolunteerGroup.create({ name: "test", - eventId: testEvent?._id, - creatorId: eventAdminUser?._id, + event: testEvent?._id, + creator: eventAdminUser?._id, volunteersRequired: 2, - leaderId: eventAdminUser?._id, + leader: eventAdminUser?._id, }); - const args: MutationUpdateEventVolunteerArgs = { + const args: MutationUpdateEventVolunteerGroupArgs = { id: testGroup2?._id.toString(), - data: {}, + data: { + eventId: testEvent?._id, + }, }; const context = { userId: eventAdminUser?._id }; + const updatedGroup = await updateEventVolunteerGroup?.({}, args, context); - const { updateEventVolunteerGroup: updateEventVolunteerGroupResolver } = - await import("../../../src/resolvers/Mutation/updateEventVolunteerGroup"); - - const updatedGroup = await updateEventVolunteerGroupResolver?.( - {}, - args, - context, - ); - - console.log(updatedGroup); - - console.log(); expect(updatedGroup).toEqual( expect.objectContaining({ name: testGroup2?.name, volunteersRequired: testGroup2?.volunteersRequired, - eventId: testGroup2?.eventId, + event: testGroup2?.event, }), ); }); diff --git a/tests/resolvers/Mutation/updatePost.spec.ts b/tests/resolvers/Mutation/updatePost.spec.ts index c57152dc7fe..d5cd9f85897 100644 --- a/tests/resolvers/Mutation/updatePost.spec.ts +++ b/tests/resolvers/Mutation/updatePost.spec.ts @@ -1,342 +1,444 @@ import "dotenv/config"; +import type mongoose from "mongoose"; import { Types } from "mongoose"; -import { AppUserProfile, Post } from "../../../src/models"; -import type { MutationUpdatePostArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../../src/db"; -import { updatePost as updatePostResolver } from "../../../src/resolvers/Mutation/updatePost"; +import type { Response } from "express"; +import { connect, disconnect } from "../../helpers/db"; + +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; import { + INTERNAL_SERVER_ERROR, LENGTH_VALIDATION_ERROR, + PLEASE_PROVIDE_TITLE, + POST_NEEDS_TO_BE_PINNED, POST_NOT_FOUND_ERROR, USER_NOT_AUTHORIZED_ERROR, USER_NOT_FOUND_ERROR, } from "../../../src/constants"; -import { beforeEach, afterEach, describe, it, expect, vi } from "vitest"; +import { AppUserProfile, Post } from "../../../src/models"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; import { - createTestOrganizationWithAdmin, createTestUser, - type TestOrganizationType, - type TestUserType, + createTestUserAndOrganization, } from "../../helpers/userAndOrg"; - +import type { InterfaceAuthenticatedRequest } from "../../../src/middleware"; +import { updatePost } from "../../../src/REST/controllers/mutation"; +import * as fileServices from "../../../src/REST/services/file"; +import type { InterfaceUploadedFileResponse } from "../../../src/REST/services/file/uploadFile"; +import { createTestPostWithMedia } from "../../helpers/posts"; import type { TestPostType } from "../../helpers/posts"; -import { createTestPost, createTestSinglePost } from "../../helpers/posts"; + +vi.mock("../../../src/libraries/requestContext", () => ({ + translate: (message: string): string => `Translated ${message}`, +})); + +/** + * module - PostUpdateControllerTests + * description - Tests for the Post Update controller functionality in a social media/blog platform + * @packageDocumentation + * + * @remarks + * Test environment uses Vitest with MongoDB for integration testing. + * File includes mock implementations for file services and translations. + * + * Key test scenarios: + * - Media attachment management (upload/delete) + * - Post content updates (text/title/pinned status) + * - Input validation (text: 500 chars, title: 256 chars) + * - Error handling (auth, post existence, pinned post rules) + * + * @see {@link updatePost} - The controller being tested + * @see {@link InterfaceAuthenticatedRequest} - Request interface + * + * @example + * ```typescript + * npm run test updatePost.test + * ``` + */ let testUser: TestUserType; -let testPost: TestPostType; let testOrganization: TestOrganizationType; -let testPost2: TestPostType; - -beforeEach(async () => { - await connect(); - const temp = await createTestPost(true); +let testPost: TestPostType; +let MONGOOSE_INSTANCE: typeof mongoose; + +interface InterfaceMockResponse extends Omit { + status(code: number): InterfaceMockResponse; + json(data: unknown): InterfaceMockResponse; +} + +const mockResponse = (): InterfaceMockResponse => { + const res = {} as InterfaceMockResponse; + res.status = vi.fn().mockReturnValue(res); + res.json = vi.fn().mockReturnValue(res); + return res; +}; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const temp = await createTestUserAndOrganization(); testUser = temp[0]; testOrganization = temp[1]; - testPost = temp[2]; - testPost2 = await createTestSinglePost(testUser?.id, testOrganization?.id); - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, - ); + // Create a test post + testPost = await createTestPostWithMedia(testUser?.id, testOrganization?._id); }); -afterEach(async () => { - await disconnect(); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> Mutation -> updatePost", () => { - it(`throws NotFoundError if no user exists with _id === context.userId`, async () => { - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - title: "newTitle", - }, - }; - - const context = { - userId: new Types.ObjectId().toString(), - }; - - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, - ); - } +describe("controllers -> post -> updatePost", () => { + afterEach(() => { + vi.clearAllMocks(); }); - it(`throws NotFoundError if no post exists with _id === args.id`, async () => { - try { - const args: MutationUpdatePostArgs = { - id: new Types.ObjectId().toString(), - }; + it("should successfully update post with file", async () => { + const mockFileId = new Types.ObjectId(); + const mockFileUploadResponse: InterfaceUploadedFileResponse = { + _id: mockFileId, + uri: "test/file/path", + visibility: "PUBLIC", + objectKey: "new-test-key", + }; - const context = { - userId: testUser?._id, - }; + vi.spyOn(fileServices, "uploadFile").mockResolvedValueOnce( + mockFileUploadResponse, + ); + vi.spyOn(fileServices, "deleteFile").mockResolvedValueOnce({ + success: true, + message: "File deleted successfully.", + }); + + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + text: "Updated text with new file", + }, + file: { + filename: "test.jpg", + mimetype: "image/jpeg", + buffer: Buffer.from("test"), + originalname: "test.jpg", + size: 1024, + }, + } as unknown as InterfaceAuthenticatedRequest; - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(POST_NOT_FOUND_ERROR.MESSAGE); - } - }); + const res = mockResponse(); + await updatePost(req, res); - it(`throws UnauthorizedError as current user with _id === context.userId is - not an creator of post with _id === args.id`, async () => { - const testUser1 = await createTestUser(); - const testOrg1 = await createTestOrganizationWithAdmin( - testUser1?._id, - false, - false, + expect(fileServices.uploadFile).toHaveBeenCalled(); + expect(fileServices.deleteFile).toHaveBeenCalledWith( + "test-file-object-key", + testPost?.file._id.toString(), + ); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + post: expect.objectContaining({ + text: "Updated text with new file", + file: mockFileId, + }), + }), ); - const testPost1 = await createTestSinglePost(testUser1?._id, testOrg1?._id); - - try { - const args: MutationUpdatePostArgs = { - id: testPost1?._id.toString() ?? "", - data: { - title: "newTitle", - }, - }; - - const context = { - userId: testUser1?._id, - }; - - await Post.updateOne( - { _id: testPost1?._id }, - { $set: { creatorId: new Types.ObjectId().toString() } }, - ); - - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } }); - it(`updates the post with _id === args.id and returns the updated post`, async () => { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - title: "newTitle", - text: "nextText", + it("should successfully update the title of a pinned post", async () => { + const pinnedPost = await createTestPostWithMedia( + testUser?.id, + testOrganization?.id, + true, + ); + + const req = { + userId: testUser?.id, + params: { id: pinnedPost?._id.toString() }, + body: { + title: "Updated Title", + pinned: true, }, - }; + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + post: expect.objectContaining({ + title: "Updated Title", + }), + }), + ); + }); - const context = { - userId: testUser?._id, - }; + it("should successfully update the pinned status of a post", async () => { + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + pinned: false, + }, + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + post: expect.objectContaining({ + pinned: false, + }), + }), + ); + }); - const updatePostPayload = await updatePostResolver?.({}, args, context); + it("should throw NotFoundError if no user exists with _id === userId", async () => { + const req = { + userId: new Types.ObjectId().toString(), + params: { id: testPost?._id.toString() }, + body: { + text: "Updated text", + }, + } as unknown as InterfaceAuthenticatedRequest; - const testUpdatePostPayload = await Post.findOne({ - _id: testPost?._id, - }).lean(); + const res = mockResponse(); + await updatePost(req, res); - expect(updatePostPayload).toEqual(testUpdatePostPayload); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + }); }); - it(`updates the post with imageUrl and returns the updated post`, async () => { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - title: "newTitle", - text: "nextText", - imageUrl: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAZSURBVBhXYzxz5sx/BiBgefLkCQMbGxsDAEdkBicg9wbaAAAAAElFTkSuQmCC", + + it("should throw NotFoundError if post does not exist", async () => { + const req = { + userId: testUser?.id, + params: { id: new Types.ObjectId().toString() }, + body: { + text: "Updated text", }, - }; + } as unknown as InterfaceAuthenticatedRequest; - const context = { - userId: testUser?._id, - }; + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${POST_NOT_FOUND_ERROR.MESSAGE}`, + }); + }); + + it("should throw UnauthorizedError if AppUserProfile is not found", async () => { + const userWithoutProfileId = await createTestUser(); + await AppUserProfile.findByIdAndDelete( + userWithoutProfileId?.appUserProfileId, + ); - const updatePostPayload = await updatePostResolver?.({}, args, context); + const req = { + userId: userWithoutProfileId?.id, + params: { id: testPost?._id.toString() }, + body: { + text: "Updated text", + }, + } as unknown as InterfaceAuthenticatedRequest; - const testUpdatePostPayload = await Post.findOne({ - _id: testPost?._id, - }).lean(); + const res = mockResponse(); + await updatePost(req, res); - expect(updatePostPayload).toEqual(testUpdatePostPayload); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + }); }); - it(`updates the post with videoUrl and returns the updated post`, async () => { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - title: "newTitle", - text: "nextText", - videoUrl: "data:video/mp4;base64,VIDEO_BASE64_DATA_HERE", + + it("should throw UnauthorizedError if user is not authorized to update post", async () => { + const [unauthorizedUser] = await createTestUserAndOrganization(); + + const req = { + userId: unauthorizedUser?.id, + params: { id: testPost?._id.toString() }, + body: { + text: "Unauthorized update", }, - }; + } as unknown as InterfaceAuthenticatedRequest; - const context = { - userId: testUser?._id, - }; + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, + }); + }); - const updatePostPayload = await updatePostResolver?.({}, args, context); + it("should throw error if trying to add title to unpinned post", async () => { + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + title: "New Title", + }, + } as unknown as InterfaceAuthenticatedRequest; - const testUpdatePostPayload = await Post.findOne({ - _id: testPost?._id, - }).lean(); + const res = mockResponse(); + await updatePost(req, res); - expect(updatePostPayload).toEqual(testUpdatePostPayload); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${POST_NEEDS_TO_BE_PINNED.MESSAGE}`, + }); }); - it(`throws String Length Validation error if title is greater than 256 characters`, async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, + + it("should throw error if removing title from pinned post", async () => { + // First create a pinned post with title + const pinnedPost = await createTestPostWithMedia( + testUser?.id, + testOrganization?.id, + true, ); - try { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - text: "random", - videoUrl: "", - title: - "AfGtN9o7IJXH9Xr5P4CcKTWMVWKOOHTldleLrWfZcThgoX5scPE5o0jARvtVA8VhneyxXquyhWb5nluW2jtP0Ry1zIOUFYfJ6BUXvpo4vCw4GVleGBnoKwkFLp5oW9L8OsEIrjVtYBwaOtXZrkTEBySZ1prr0vFcmrSoCqrCTaChNOxL3tDoHK6h44ChFvgmoVYMSq3IzJohKtbBn68D9NfEVMEtoimkGarUnVBAOsGkKv0mIBJaCl2pnR8Xwq1cG1", - imageUrl: null, - }, - }; - - const context = { - userId: testUser?.id, - }; - - const { updatePost: updatePostResolver } = await import( - "../../../src/resolvers/Mutation/updatePost" - ); - - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, - ); - } + const req = { + userId: testUser?.id, + params: { id: pinnedPost?.id.toString() }, + body: { + title: "", + pinned: true, + }, + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${PLEASE_PROVIDE_TITLE.MESSAGE}`, + }); }); - it(`throws String Length Validation error if text is greater than 500 characters`, async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, + + it("should throw error if title exceeds maximum length", async () => { + const testPost = await createTestPostWithMedia( + testUser?.id, + testOrganization?._id, + true, ); - try { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - text: "JWQPfpdkGGGKyryb86K4YN85nDj4m4F7gEAMBbMXLax73pn2okV6kpWY0EYO0XSlUc0fAlp45UCgg3s6mqsRYF9FOlzNIDFLZ1rd03Z17cdJRuvBcAmbC0imyqGdXHGDUQmVyOjDkaOLAvjhB5uDeuEqajcAPTcKpZ6LMpigXuqRAd0xGdPNXyITC03FEeKZAjjJL35cSIUeMv5eWmiFlmmm70FU1Bp6575zzBtEdyWPLflcA2GpGmmf4zvT7nfgN3NIkwQIhk9OwP8dn75YYczcYuUzLpxBu1Lyog77YlAj5DNdTIveXu9zHeC6V4EEUcPQtf1622mhdU3jZNMIAyxcAG4ErtztYYRqFs0ApUxXiQI38rmiaLcicYQgcOxpmFvqRGiSduiCprCYm90CHWbQFq4w2uhr8HhR3r9HYMIYtrRyO6C3rPXaQ7otpjuNgE0AKI57AZ4nGG1lvNwptFCY60JEndSLX9Za6XP1zkVRLaMZArQNl", - videoUrl: "", - title: "random", - imageUrl: null, - }, - }; - - const context = { - userId: testUser?.id, - }; - - const { updatePost: updatePostResolver } = await import( - "../../../src/resolvers/Mutation/updatePost" - ); - - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in information`, - ); - } + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + title: "a".repeat(257), + pinned: true, + }, + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, + }); }); - it("throws error if title is provided and post is not pinned", async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, - ); - try { - const args: MutationUpdatePostArgs = { - id: testPost2?._id.toString() || "", - data: { - title: "Test title", - text: "Test text", - }, - }; - - const context = { - userId: testUser?.id, - }; - - const { updatePost: updatePostResolver } = await import( - "../../../src/resolvers/Mutation/updatePost" - ); - - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - `Post needs to be pinned inorder to add a title`, - ); - } + it("should throw error if text exceeds maximum length", async () => { + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + text: "a".repeat(501), + }, + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in information`, + }); }); - it(`throws error if title is not provided and post is pinned`, async () => { - const { requestContext } = await import("../../../src/libraries"); - vi.spyOn(requestContext, "translate").mockImplementationOnce( - (message) => message, + it("should successfully update post text", async () => { + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + text: "Updated post text", + }, + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + post: expect.objectContaining({ + text: "Updated post text", + }), + }), ); - try { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - text: "Testing text", - }, - }; - - const context = { - userId: testUser?.id, - }; - - const { updatePost: updatePostResolver } = await import( - "../../../src/resolvers/Mutation/updatePost" - ); - - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - `Please provide a title to pin post`, - ); - } }); - it("throws error if AppUserProfile is not found", async () => { - const userWithoutProfileId = await createTestUser(); - await AppUserProfile.findByIdAndDelete( - userWithoutProfileId?.appUserProfileId, + it("should handle file upload error gracefully", async () => { + vi.spyOn(fileServices, "uploadFile").mockRejectedValueOnce( + new Error("Upload failed"), ); - const { requestContext } = await import("../../../src/libraries"); - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - try { - const args: MutationUpdatePostArgs = { - id: testPost?._id.toString() || "", - data: { - title: "newTitle", - }, - }; - - const context = { - userId: userWithoutProfileId?._id, - }; - - await updatePostResolver?.({}, args, context); - } catch (error: unknown) { - expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`, - ); - } + + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + text: "Updated text with file", + }, + file: { + filename: "test.jpg", + mimetype: "image/jpeg", + buffer: Buffer.from("test"), + originalname: "test.jpg", + size: 1024, + }, + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(fileServices.uploadFile).toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: "Upload failed", + }); + }); + + it("should handle non-Error type errors with internal server error message", async () => { + vi.spyOn(Post, "findOneAndUpdate").mockImplementationOnce(() => { + throw "Some unknown error"; + }); + + const req = { + userId: testUser?.id, + params: { id: testPost?._id.toString() }, + body: { + text: "Updated text", + }, + } as unknown as InterfaceAuthenticatedRequest; + + const res = mockResponse(); + await updatePost(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: `Translated ${INTERNAL_SERVER_ERROR.MESSAGE}`, + }); }); }); diff --git a/tests/resolvers/Mutation/updateUserProfile.spec.ts b/tests/resolvers/Mutation/updateUserProfile.spec.ts index 5cfe090aa1c..e3657c28b5d 100644 --- a/tests/resolvers/Mutation/updateUserProfile.spec.ts +++ b/tests/resolvers/Mutation/updateUserProfile.spec.ts @@ -6,6 +6,7 @@ import type { InterfaceUser } from "../../../src/models"; import { User } from "../../../src/models"; import type { MutationUpdateUserProfileArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; +import * as userCache from "../../../src/services/UserCache/deleteUserFromCache"; import { nanoid } from "nanoid"; import { @@ -23,7 +24,6 @@ import { USER_NOT_FOUND_ERROR, } from "../../../src/constants"; import { updateUserProfile as updateUserProfileResolver } from "../../../src/resolvers/Mutation/updateUserProfile"; -import { deleteUserFromCache } from "../../../src/services/UserCache/deleteUserFromCache"; import * as uploadEncodedImage from "../../../src/utilities/encodedImageStorage/uploadEncodedImage"; let MONGOOSE_INSTANCE: typeof mongoose; @@ -302,75 +302,6 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { }); }); - it("When Image is give updates the current user's object with the uploaded image and returns it", async () => { - const args: MutationUpdateUserProfileArgs = { - data: {}, - file: "newImageFile.png", - }; - - vi.spyOn(uploadEncodedImage, "uploadEncodedImage").mockImplementation( - async (encodedImageURL: string) => encodedImageURL, - ); - - const context = { - userId: testUser._id, - apiRootUrl: BASE_URL, - }; - await deleteUserFromCache(testUser._id.toString() || ""); - - const updateUserProfilePayload = await updateUserProfileResolver?.( - {}, - args, - context, - ); - - expect(updateUserProfilePayload).toEqual({ - ...testUser.toObject(), - email: updateUserProfilePayload?.email, - firstName: "newFirstName", - lastName: "newLastName", - image: BASE_URL + "newImageFile.png", - updatedAt: expect.anything(), - createdAt: expect.anything(), - }); - }); - - it("When Image is give updates the current user's object with the uploaded image and returns it", async () => { - const args: MutationUpdateUserProfileArgs = { - data: { - email: `email${nanoid().toLowerCase()}@gmail.com`, - firstName: "newFirstName", - lastName: "newLastName", - }, - file: "newImageFile.png", - }; - - vi.spyOn(uploadEncodedImage, "uploadEncodedImage").mockImplementation( - async (encodedImageURL: string) => encodedImageURL, - ); - - const context = { - userId: testUser._id, - apiRootUrl: BASE_URL, - }; - - const updateUserProfilePayload = await updateUserProfileResolver?.( - {}, - args, - context, - ); - - expect(updateUserProfilePayload).toEqual({ - ...testUser.toObject(), - email: args.data?.email, - firstName: "newFirstName", - lastName: "newLastName", - image: BASE_URL + args.file, - updatedAt: expect.anything(), - createdAt: expect.anything(), - }); - }); - it(`updates current user's user object when any single argument(birthdate) is given w/0 changing other fields `, async () => { const args: MutationUpdateUserProfileArgs = { data: { @@ -396,7 +327,7 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: testUserobj?.email, firstName: testUserobj?.firstName, lastName: testUserobj?.lastName, - image: BASE_URL + "newImageFile.png", + image: null, birthDate: args.data?.birthDate, updatedAt: expect.anything(), createdAt: expect.anything(), @@ -428,7 +359,7 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: testUserobj?.email, firstName: testUserobj?.firstName, lastName: testUserobj?.lastName, - image: BASE_URL + "newImageFile.png", + image: null, birthDate: date, educationGrade: args.data?.educationGrade, updatedAt: expect.anything(), @@ -461,7 +392,7 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: testUserobj?.email, firstName: testUserobj?.firstName, lastName: testUserobj?.lastName, - image: BASE_URL + "newImageFile.png", + image: null, birthDate: date, educationGrade: "GRADUATE", employmentStatus: args.data?.employmentStatus, @@ -495,7 +426,7 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: testUserobj?.email, firstName: testUserobj?.firstName, lastName: testUserobj?.lastName, - image: BASE_URL + "newImageFile.png", + image: null, birthDate: date, educationGrade: "GRADUATE", employmentStatus: "FULL_TIME", @@ -530,7 +461,7 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: testUserobj?.email, firstName: testUserobj?.firstName, lastName: testUserobj?.lastName, - image: BASE_URL + "newImageFile.png", + image: null, birthDate: date, educationGrade: "GRADUATE", employmentStatus: "FULL_TIME", @@ -570,7 +501,7 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: testUserobj?.email, firstName: testUserobj?.firstName, lastName: testUserobj?.lastName, - image: BASE_URL + "newImageFile.png", + image: null, birthDate: date, educationGrade: "GRADUATE", employmentStatus: "FULL_TIME", @@ -616,7 +547,7 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: testUserobj?.email, firstName: testUserobj?.firstName, lastName: testUserobj?.lastName, - image: BASE_URL + "newImageFile.png", + image: null, birthDate: date, educationGrade: "GRADUATE", employmentStatus: "FULL_TIME", @@ -668,6 +599,10 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { async (encodedImageURL: string) => encodedImageURL, ); + const deleteFromCacheSpy = vi + .spyOn(userCache, "deleteUserFromCache") + .mockImplementation(async () => Promise.resolve()); + const context = { userId: testUser._id, apiRootUrl: BASE_URL, @@ -679,12 +614,15 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { context, ); + expect(deleteFromCacheSpy).toHaveBeenCalledWith( + updateUserProfilePayload?._id.toString(), + ); expect(updateUserProfilePayload).toEqual({ ...testUser.toObject(), email: args.data?.email, firstName: args.data?.firstName, lastName: args.data?.lastName, - image: BASE_URL + args.file, + image: args.file, birthDate: date, educationGrade: args.data?.educationGrade, employmentStatus: args.data?.employmentStatus, diff --git a/tests/resolvers/Mutation/updateVolunteerMembership.spec.ts b/tests/resolvers/Mutation/updateVolunteerMembership.spec.ts new file mode 100644 index 00000000000..348998e37e3 --- /dev/null +++ b/tests/resolvers/Mutation/updateVolunteerMembership.spec.ts @@ -0,0 +1,175 @@ +import type mongoose from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { + TestEventType, + TestEventVolunteerGroupType, + TestEventVolunteerType, +} from "../../helpers/events"; +import { createTestUser, type TestUserType } from "../../helpers/user"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import { VolunteerMembership } from "../../../src/models"; +import { updateVolunteerMembership } from "../../../src/resolvers/Mutation/updateVolunteerMembership"; +import { Types } from "mongoose"; +import { + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../../src/constants"; +import { requestContext } from "../../../src/libraries"; +import { MembershipStatus } from "../Query/getVolunteerMembership.spec"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testEvent: TestEventType; +let testUser1: TestUserType; +let testEventVolunteer1: TestEventVolunteerType; +let testEventVolunteerGroup: TestEventVolunteerGroupType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + vi.spyOn(requestContext, "translate").mockImplementation( + (message) => message, + ); + const [, event, user1, , volunteer1, , volunteerGroup, ,] = + await createVolunteerAndActions(); + + testEvent = event; + testUser1 = user1; + testEventVolunteer1 = volunteer1; + testEventVolunteerGroup = volunteerGroup; + + await VolunteerMembership.insertMany([ + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + status: MembershipStatus.INVITED, + }, + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + group: testEventVolunteerGroup._id, + status: MembershipStatus.REQUESTED, + }, + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + status: MembershipStatus.ACCEPTED, + }, + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + status: MembershipStatus.REJECTED, + }, + ]); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Mutation -> updateVolunteerMembership", () => { + it("throws NotFoundError if current User does not exist", async () => { + try { + const membership = await VolunteerMembership.findOne({ + status: MembershipStatus.REQUESTED, + group: testEventVolunteerGroup._id, + volunteer: testEventVolunteer1?._id, + }); + + await updateVolunteerMembership?.( + {}, + { + id: membership?._id.toString() ?? "", + status: MembershipStatus.ACCEPTED, + }, + { userId: new Types.ObjectId().toString() }, + ); + } catch (error: unknown) { + expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it("throws NotFoundError if VolunteerMembership does not exist", async () => { + try { + await VolunteerMembership.findOne({ + status: MembershipStatus.REQUESTED, + group: testEventVolunteerGroup._id, + volunteer: testEventVolunteer1?._id, + }); + + await updateVolunteerMembership?.( + {}, + { + id: new Types.ObjectId().toString() ?? "", + status: MembershipStatus.ACCEPTED, + }, + { userId: testUser1?._id }, + ); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR.MESSAGE, + ); + } + }); + + it("throws UnauthorizedUser Error", async () => { + try { + const membership = await VolunteerMembership.findOne({ + status: MembershipStatus.REJECTED, + volunteer: testEventVolunteer1?._id, + }); + + const randomUser = await createTestUser(); + + await updateVolunteerMembership?.( + {}, + { + id: membership?._id.toString() as string, + status: MembershipStatus.ACCEPTED, + }, + { userId: randomUser?._id.toString() as string }, + ); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + USER_NOT_AUTHORIZED_ERROR.MESSAGE, + ); + } + }); + + it(`updateVolunteerMembership (with group) - set to accepted `, async () => { + const membership = await VolunteerMembership.findOne({ + status: MembershipStatus.INVITED, + volunteer: testEventVolunteer1?._id, + }); + + const updatedMembership = await updateVolunteerMembership?.( + {}, + { + id: membership?._id.toString() ?? "", + status: MembershipStatus.REJECTED, + }, + { userId: testUser1?._id }, + ); + + expect(updatedMembership?.status).toEqual(MembershipStatus.REJECTED); + }); + + it(`updateVolunteerMembership (with group) - set to accepted `, async () => { + const membership = await VolunteerMembership.findOne({ + status: MembershipStatus.REQUESTED, + group: testEventVolunteerGroup._id, + volunteer: testEventVolunteer1?._id, + }); + + const updatedMembership = await updateVolunteerMembership?.( + {}, + { + id: membership?._id.toString() ?? "", + status: MembershipStatus.ACCEPTED, + }, + { userId: testUser1?._id }, + ); + + expect(updatedMembership?.status).toEqual(MembershipStatus.ACCEPTED); + }); +}); diff --git a/tests/resolvers/Organization/image.spec.ts b/tests/resolvers/Organization/image.spec.ts index 81124c80dc0..f94db31e7b0 100644 --- a/tests/resolvers/Organization/image.spec.ts +++ b/tests/resolvers/Organization/image.spec.ts @@ -66,7 +66,7 @@ describe("resolvers -> Organization -> image", () => { const org = await Organization.findOne({ _id: parent._id, }); - expect(creatorPayload).toEqual("http://testdomain.com" + org?.image); + expect(creatorPayload).toEqual(org?.image); } }); it(`returns null if the image is null in the organization`, async () => { diff --git a/tests/resolvers/Organization/posts.spec.ts b/tests/resolvers/Organization/posts.spec.ts index 74bb6a8d69f..a71ec1d322e 100644 --- a/tests/resolvers/Organization/posts.spec.ts +++ b/tests/resolvers/Organization/posts.spec.ts @@ -70,54 +70,23 @@ describe("resolvers -> Organization -> post", () => { creatorId: testUser?._id, }).countDocuments(); - const context = { apiRootUrl: "http://example.com" }; - - const formattedPost2 = { - ...testPost2?.toObject(), - imageUrl: testPost2?.imageUrl - ? new URL(testPost2.imageUrl, context.apiRootUrl).toString() - : null, - videoUrl: testPost2?.videoUrl - ? new URL(testPost2.videoUrl, context.apiRootUrl).toString() - : null, - }; - - const formattedPost = { - ...testPost?.toObject(), - imageUrl: testPost?.imageUrl - ? new URL(testPost.imageUrl, context.apiRootUrl).toString() - : null, - videoUrl: testPost?.videoUrl - ? new URL(testPost.videoUrl, context.apiRootUrl).toString() - : null, - }; + const formattedPost2 = testPost2?.toObject(); + const formattedPost = testPost?.toObject(); expect(connection).toEqual({ edges: [ { - cursor: formattedPost2._id?.toString(), + cursor: formattedPost2?._id?.toString(), node: { ...formattedPost2, - _id: formattedPost2._id?.toString(), - imageUrl: testPost?.imageUrl - ? new URL(testPost.imageUrl, context.apiRootUrl).toString() - : null, - videoUrl: formattedPost2?.videoUrl - ? new URL(formattedPost2.videoUrl, context.apiRootUrl).toString() - : null, + _id: formattedPost2?._id?.toString(), }, }, { - cursor: formattedPost._id?.toString(), + cursor: formattedPost?._id?.toString(), node: { ...formattedPost, _id: formattedPost?._id?.toString(), - imageUrl: formattedPost?.imageUrl - ? new URL(formattedPost.imageUrl, context.apiRootUrl).toString() - : null, - videoUrl: formattedPost?.videoUrl - ? new URL(formattedPost.videoUrl, context.apiRootUrl).toString() - : null, }, }, ], @@ -165,4 +134,48 @@ describe("parseCursor function", () => { expect(result.parsedCursor).toEqual(testPost?._id.toString()); } }); + it("throws GraphQLError when an invalid cursor is provided", async () => { + const parent = { + _id: testOrganization?._id, + } as InterfaceOrganization; + + try { + await postResolver?.( + parent, + { + first: 2, + after: new Types.ObjectId().toString(), // Invalid cursor + }, + {}, + ); + // If we reach here, the test should fail because an error should have been thrown + expect(true).toBe(false); + } catch (error) { + expect(error).toBeInstanceOf(GraphQLError); + if (error instanceof GraphQLError) { + expect(error.extensions.code).toEqual("INVALID_ARGUMENTS"); + expect( + (error.extensions.errors as DefaultGraphQLArgumentError[]).length, + ).toBeGreaterThan(0); + } + } + }); + + it("successfully uses parseCursor with valid cursor", async () => { + const parent = { + _id: testOrganization?._id, + } as InterfaceOrganization; + + const connection = await postResolver?.( + parent, + { + first: 2, + after: testPost2?._id.toString(), // Valid cursor + }, + {}, + ); + + expect(connection).toBeDefined(); + expect(connection?.edges.length).toBe(1); + }); }); diff --git a/tests/resolvers/Organization/userTags.spec.ts b/tests/resolvers/Organization/userTags.spec.ts index d3d3e9b948e..bc7409611af 100644 --- a/tests/resolvers/Organization/userTags.spec.ts +++ b/tests/resolvers/Organization/userTags.spec.ts @@ -12,17 +12,44 @@ import { import type { DefaultGraphQLArgumentError } from "../../../src/utilities/graphQLConnection"; import { connect, disconnect } from "../../helpers/db"; import type { TestUserTagType } from "../../helpers/tags"; -import { createRootTagsWithOrg } from "../../helpers/tags"; +import { + createRootTagsWithOrg, + createTwoLevelTagsWithOrg, +} from "../../helpers/tags"; import type { TestOrganizationType } from "../../helpers/userAndOrg"; let MONGOOSE_INSTANCE: typeof mongoose; let testUserTag1: TestUserTagType, testUserTag2: TestUserTagType; +let testRootTag: TestUserTagType, testSubTag: TestUserTagType; let testOrganization: TestOrganizationType; +let testOrganization2: TestOrganizationType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); [, testOrganization, [testUserTag1, testUserTag2]] = await createRootTagsWithOrg(2); + [, testOrganization2, [testRootTag, testSubTag]] = + await createTwoLevelTagsWithOrg(); + + testRootTag = await OrganizationTagUser.findOneAndUpdate( + { + _id: testRootTag?._id, + }, + { + name: "testRootTag", + }, + { new: true }, + ).lean(); + + testSubTag = await OrganizationTagUser.findOneAndUpdate( + { + _id: testSubTag?._id, + }, + { + name: "testSubTag", + }, + { new: true }, + ).lean(); }); afterAll(async () => { @@ -44,7 +71,7 @@ describe("userTags resolver", () => { } }); - it(`returns the expected connection object`, async () => { + it(`returns the expected connection object, i.e. all the root tags`, async () => { const parent = testOrganization?.toObject() as InterfaceOrganization; const connection = await userTagsResolver?.( @@ -85,6 +112,51 @@ describe("userTags resolver", () => { totalCount, }); }); + + it(`returns all the tags (including nested tags), if the where input is defined`, async () => { + const parent = testOrganization2?.toObject() as InterfaceOrganization; + + const connection = await userTagsResolver?.( + parent, + { + first: 2, + where: { name: { starts_with: "test" } }, + sortedBy: { id: "ASCENDING" }, + }, + {}, + ); + + const totalCount = await OrganizationTagUser.find({ + name: new RegExp("^test", "i"), + organizationId: testOrganization2?._id, + }).countDocuments(); + + expect(connection).toEqual({ + edges: [ + { + cursor: testRootTag?._id.toString(), + node: { + ...testRootTag, + _id: testRootTag?._id.toString(), + }, + }, + { + cursor: testSubTag?._id.toString(), + node: { + ...testSubTag, + _id: testSubTag?._id.toString(), + }, + }, + ], + pageInfo: { + endCursor: testSubTag?._id.toString(), + hasNextPage: false, + hasPreviousPage: false, + startCursor: testRootTag?._id.toString(), + }, + totalCount, + }); + }); }); describe("parseCursor function", () => { diff --git a/tests/resolvers/Post/likedBy.spec.ts b/tests/resolvers/Post/likedBy.spec.ts new file mode 100644 index 00000000000..5c768353ee8 --- /dev/null +++ b/tests/resolvers/Post/likedBy.spec.ts @@ -0,0 +1,82 @@ +import "dotenv/config"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { TestPostType } from "../../helpers/posts"; +import { createTestPost } from "../../helpers/posts"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; +import type { InterfacePost } from "../../../src/models"; +import { Organization, Post, User } from "../../../src/models"; +import { posts as postResolver } from "../../../src/resolvers/Organization/posts"; + +let testPost: TestPostType; +let testUser: TestUserType; +let testOrganization: TestOrganizationType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [testUser, testOrganization, testPost] = await createTestPost(); + + await User.findByIdAndUpdate(testUser?._id, { + $set: { + image: "exampleimageurl.com", + }, + }); + + await Post.updateOne( + { + _id: testPost?._id, + }, + { + $push: { + likedBy: testUser?._id, + }, + $inc: { + likeCount: 1, + commentCount: 1, + }, + }, + ); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> organization -> posts", () => { + it(`returns the post object for parent post`, async () => { + const parent = await Organization.findById(testOrganization?._id).lean(); + + if (!parent) { + throw new Error("Parent organization not found."); + } + + const postPayload = (await postResolver?.(parent, { first: 1 }, {})) as { + edges: { node: InterfacePost }[]; + totalCount: number; + }; + + expect(postPayload).toBeDefined(); + if (!postPayload) { + throw new Error("postPayload is null or undefined"); + } + expect(postPayload.edges).toBeDefined(); + expect(Array.isArray(postPayload.edges)).toBe(true); + + const posts = await Post.find({ + organization: testOrganization?._id, + }).lean(); + + expect(postPayload.edges.length).toEqual(posts.length); + expect(postPayload.totalCount).toEqual(posts.length); + const returnedPost = postPayload.edges[0].node; + expect(returnedPost._id).toEqual(testPost?._id.toString()); + expect(returnedPost.likedBy).toHaveLength(1); + expect(returnedPost.likedBy[0]._id).toEqual(testUser?._id); + expect(returnedPost.likedBy[0].image).not.toBeNull(); + }); +}); diff --git a/tests/resolvers/Query/actionItemsByOrganization.spec.ts b/tests/resolvers/Query/actionItemsByOrganization.spec.ts index 8437ad5cdd2..36cca125836 100644 --- a/tests/resolvers/Query/actionItemsByOrganization.spec.ts +++ b/tests/resolvers/Query/actionItemsByOrganization.spec.ts @@ -1,31 +1,46 @@ -import "dotenv/config"; -import type { InterfaceActionItem } from "../../../src/models"; -import { ActionItem, ActionItemCategory } from "../../../src/models"; +import type mongoose from "mongoose"; import { connect, disconnect } from "../../helpers/db"; -import type { - ActionItemWhereInput, - ActionItemsOrderByInput, - QueryActionItemsByOrganizationArgs, -} from "../../../src/types/generatedGraphQLTypes"; -import { actionItemsByOrganization as actionItemsByOrganizationResolver } from "../../../src/resolvers/Query/actionItemsByOrganization"; import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type mongoose from "mongoose"; -import { createTestActionItems } from "../../helpers/actionItem"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; import type { TestEventType } from "../../helpers/events"; +import type { TestUserType } from "../../helpers/user"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import type { InterfaceActionItem } from "../../../src/models"; +import { ActionItem } from "../../../src/models"; +import type { TestActionItemType } from "../../helpers/actionItem"; +import { actionItemsByOrganization } from "../../../src/resolvers/Query/actionItemsByOrganization"; let MONGOOSE_INSTANCE: typeof mongoose; let testOrganization: TestOrganizationType; let testEvent: TestEventType; -let testAssigneeUser: TestUserType; +let testUser1: TestUserType; +let testActionItem1: TestActionItemType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - [, testAssigneeUser, testEvent, testOrganization] = - await createTestActionItems(); + const [organization, event, user1, , , , , actionItem1] = + await createVolunteerAndActions(); + + testOrganization = organization; + testEvent = event; + testUser1 = user1; + testActionItem1 = actionItem1; + + await ActionItem.create({ + creator: testUser1?._id, + assigner: testUser1?._id, + assigneeUser: testUser1?._id, + assigneeType: "User", + assignee: null, + assigneeGroup: null, + actionItemCategory: testActionItem1.actionItemCategory, + event: null, + organization: testOrganization?._id, + allottedHours: 2, + assignmentDate: new Date(), + dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 3000), + isCompleted: false, + }); }); afterAll(async () => { @@ -33,238 +48,37 @@ afterAll(async () => { }); describe("resolvers -> Query -> actionItemsByOrganization", () => { - it(`returns list of all action items associated with an organization in ascending order where eventId is not null`, async () => { - const orderBy: ActionItemsOrderByInput = "createdAt_ASC"; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - eventId: testEvent?._id, - orderBy, - }; - - const actionItemsByOrganizationPayload = - (await actionItemsByOrganizationResolver?.( - {}, - args, - {}, - )) as InterfaceActionItem[]; - - const actionItemsByOrganizationInfo = await ActionItem.find({ - organization: args.organizationId, - event: args.eventId, - }).lean(); - - expect(actionItemsByOrganizationPayload[0]).toEqual( - expect.objectContaining({ - _id: actionItemsByOrganizationInfo[0]._id, - }), - ); - }); - - it(`returns list of all action items associated with an organization in ascending order where eventId is null`, async () => { - const orderBy: ActionItemsOrderByInput = "createdAt_ASC"; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - eventId: null, - orderBy, - }; - - const actionItemsByOrganizationPayload = - (await actionItemsByOrganizationResolver?.( - {}, - args, - {}, - )) as InterfaceActionItem[]; - - const actionItemsByOrganizationInfo = await ActionItem.find({ - organization: args.organizationId, - event: args.eventId, - }).lean(); - - expect(actionItemsByOrganizationPayload[0]).toEqual( - expect.objectContaining({ - _id: actionItemsByOrganizationInfo[0]._id, - }), - ); - }); - - it(`returns list of all action items associated with an organization in descending order`, async () => { - const orderBy: ActionItemsOrderByInput = "createdAt_DESC"; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - eventId: null, - orderBy, - }; - - const actionItemsByOrganizationPayload = - (await actionItemsByOrganizationResolver?.( - {}, - args, - {}, - )) as InterfaceActionItem[]; - - const actionItemsByOrganizationInfo = await ActionItem.find({ - organization: args.organizationId, - event: args.eventId, - }).lean(); - - expect(actionItemsByOrganizationPayload[0]).toEqual( - expect.objectContaining({ - _id: actionItemsByOrganizationInfo[0]._id, - }), + it(`actionItemsByOrganization - organizationId, eventId, assigneeName`, async () => { + const actionItems = (await actionItemsByOrganization?.( + {}, + { + organizationId: testOrganization?._id, + eventId: testEvent?._id, + where: { + categoryName: "Test Action Item Category 1", + assigneeName: testUser1?.firstName, + }, + }, + {}, + )) as unknown as InterfaceActionItem[]; + expect(actionItems[0].assigneeType).toEqual("EventVolunteer"); + expect(actionItems[0].assignee.user.firstName).toEqual( + testUser1?.firstName, ); }); - it(`returns list of all action items associated with an organization and belonging to an action item category`, async () => { - const actionItemCategories = await ActionItemCategory.find({ - organizationId: testOrganization?._id, - }); - - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, - ); - - const actionItemCategoryId = actionItemCategoriesIds[0]; - - const where: ActionItemWhereInput = { - actionItemCategory_id: actionItemCategoryId.toString(), - }; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - where, - }; - - const actionItemsByOrganizationPayload = - await actionItemsByOrganizationResolver?.({}, args, {}); - - const actionItemsByOrganizationInfo = await ActionItem.find({ - actionItemCategoryId, - }).lean(); - - expect(actionItemsByOrganizationPayload).toEqual( - actionItemsByOrganizationInfo, - ); - }); - it(`returns list of all action items associated with an organization that are active`, async () => { - const where: ActionItemWhereInput = { - is_completed: false, - }; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - eventId: testEvent?._id, - where, - }; - - const actionItemsByOrganizationPayload = - (await actionItemsByOrganizationResolver?.( - {}, - args, - {}, - )) as InterfaceActionItem[]; - - const actionItemsByOrganizationInfo = await ActionItem.find({ - organization: args.organizationId, - event: args.eventId, - }).lean(); - - expect(actionItemsByOrganizationPayload[0]).toEqual( - expect.objectContaining({ - _id: actionItemsByOrganizationInfo[1]._id, - }), - ); - }); - - it(`returns list of all action items associated with an organization that are completed`, async () => { - const where: ActionItemWhereInput = { - is_completed: true, - }; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - eventId: testEvent?._id, - where, - }; - - const actionItemsByOrganizationPayload = - (await actionItemsByOrganizationResolver?.( - {}, - args, - {}, - )) as InterfaceActionItem[]; - - const actionItemsByOrganizationInfo = await ActionItem.find({ - organization: args.organizationId, - event: args.eventId, - }).lean(); - - expect(actionItemsByOrganizationPayload[0]).toEqual( - expect.objectContaining({ - _id: actionItemsByOrganizationInfo[0]._id, - }), - ); - }); - - it(`returns list of all action items matching categoryName Filter`, async () => { - const where: ActionItemWhereInput = { - categoryName: "Default", - }; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - eventId: testEvent?._id, - where, - }; - - const actionItemsByOrganizationPayload = - (await actionItemsByOrganizationResolver?.( - {}, - args, - {}, - )) as InterfaceActionItem[]; - - const actionItemsByOrganizationInfo = await ActionItem.find({ - organization: args.organizationId, - event: args.eventId, - }).lean(); - - expect(actionItemsByOrganizationPayload[0].actionItemCategory).toEqual( - expect.objectContaining({ - _id: actionItemsByOrganizationInfo[0].actionItemCategory, - }), - ); - }); - - it(`returns list of all action items matching assigneeName Filter`, async () => { - const where: ActionItemWhereInput = { - assigneeName: testAssigneeUser?.firstName, - }; - - const args: QueryActionItemsByOrganizationArgs = { - organizationId: testOrganization?._id, - eventId: testEvent?._id, - where, - }; - - const actionItemsByOrganizationPayload = - (await actionItemsByOrganizationResolver?.( - {}, - args, - {}, - )) as InterfaceActionItem[]; - - const actionItemsByOrganizationInfo = await ActionItem.find({ - organization: args.organizationId, - event: args.eventId, - }).lean(); - - expect(actionItemsByOrganizationPayload[0].assignee).toEqual( - expect.objectContaining({ - _id: actionItemsByOrganizationInfo[0].assignee, - }), - ); + it(`actionItemsByOrganization - organizationId, assigneeName`, async () => { + const actionItems = (await actionItemsByOrganization?.( + {}, + { + organizationId: testOrganization?._id, + where: { + assigneeName: testUser1?.firstName, + }, + }, + {}, + )) as unknown as InterfaceActionItem[]; + expect(actionItems[0].assigneeType).toEqual("User"); + expect(actionItems[0].assigneeUser.firstName).toEqual(testUser1?.firstName); }); }); diff --git a/tests/resolvers/Query/actionItemsByUser.spec.ts b/tests/resolvers/Query/actionItemsByUser.spec.ts new file mode 100644 index 00000000000..e5929381b68 --- /dev/null +++ b/tests/resolvers/Query/actionItemsByUser.spec.ts @@ -0,0 +1,98 @@ +import type mongoose from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { TestUserType } from "../../helpers/user"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import type { InterfaceActionItem } from "../../../src/models"; +import { ActionItem } from "../../../src/models"; +import type { TestActionItemType } from "../../helpers/actionItem"; +import { actionItemsByUser } from "../../../src/resolvers/Query/actionItemsByUser"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testOrganization: TestOrganizationType; +let testUser1: TestUserType; +let testActionItem1: TestActionItemType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const [organization, , user1, , , , , actionItem1] = + await createVolunteerAndActions(); + + testOrganization = organization; + testUser1 = user1; + testActionItem1 = actionItem1; + + await ActionItem.create({ + creator: testUser1?._id, + assigner: testUser1?._id, + assigneeUser: testUser1?._id, + assigneeType: "User", + assignee: null, + assigneeGroup: null, + actionItemCategory: testActionItem1.actionItemCategory, + event: null, + organization: testOrganization?._id, + allottedHours: 2, + assignmentDate: new Date(), + dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 3000), + isCompleted: false, + }); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> actionItemsByUser", () => { + it(`actionItemsByUser for userId, categoryName, dueDate_ASC`, async () => { + const actionItems = (await actionItemsByUser?.( + {}, + { + userId: testUser1?._id.toString() ?? "testUserId", + orderBy: "dueDate_ASC", + where: { + categoryName: "Test Action Item Category 1", + orgId: testOrganization?._id.toString(), + }, + }, + {}, + )) as unknown as InterfaceActionItem[]; + expect(actionItems[0].assigneeType).toEqual("EventVolunteer"); + expect(actionItems[1].assigneeType).toEqual("EventVolunteerGroup"); + }); + + it(`actionItemsByUser for userId, assigneeName, dueDate_DESC`, async () => { + const actionItems = (await actionItemsByUser?.( + {}, + { + userId: testUser1?._id.toString() ?? "testUserId", + orderBy: "dueDate_DESC", + where: { + categoryName: "Test Action Item Category 1", + assigneeName: testUser1?.firstName, + orgId: testOrganization?._id.toString(), + }, + }, + {}, + )) as unknown as InterfaceActionItem[]; + expect(actionItems[1].assignee.user.firstName).toEqual( + testUser1?.firstName, + ); + }); + + it(`actionItemsByUser for userId, assigneeName doesn't match`, async () => { + const actionItems = (await actionItemsByUser?.( + {}, + { + userId: testUser1?._id.toString() ?? "testUserId", + where: { + assigneeName: "xyz", + orgId: testOrganization?._id.toString(), + }, + }, + {}, + )) as unknown as InterfaceActionItem[]; + expect(actionItems.length).toEqual(0); + }); +}); diff --git a/tests/resolvers/Query/chatById.spec.ts b/tests/resolvers/Query/chatById.spec.ts new file mode 100644 index 00000000000..9adb1c22ead --- /dev/null +++ b/tests/resolvers/Query/chatById.spec.ts @@ -0,0 +1,37 @@ +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { chatById } from "../../../src/resolvers/Query/chatById"; +import { createTestChat, type TestChatType } from "../../helpers/chat"; +import { connect, disconnect } from "../../helpers/db"; +let MONGOOSE_INSTANCE: typeof mongoose; +let testChat: TestChatType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const temp = await createTestChat(); + testChat = temp[2]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); +describe("resolvers->Query->chatById", () => { + it(`returns the chat with _id === args.id`, async () => { + const args = { + id: testChat?._id?.toString() ?? "", + }; + const chatByIdPayload = await chatById?.({}, args, {}); + expect(chatByIdPayload).toEqual(testChat?.toObject()); + }); + it(`throws chat not found if chat not found for args.id`, async () => { + try { + const args = { + id: new Types.ObjectId().toString(), + }; + await chatById?.({}, args, {}); + } catch (error: unknown) { + expect((error as Error).message).toEqual("Chat not found"); + } + }); +}); diff --git a/tests/resolvers/Query/chatsByuserId.spec.ts b/tests/resolvers/Query/chatsByuserId.spec.ts new file mode 100644 index 00000000000..c07ba1dfe24 --- /dev/null +++ b/tests/resolvers/Query/chatsByuserId.spec.ts @@ -0,0 +1,54 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; + +import { chatsByUserId as chatsByUserIdResolver } from "../../../src/resolvers/Query/chatsByUserId"; +import { Chat } from "../../../src/models"; +import type { QueryChatsByUserIdArgs } from "../../../src/types/generatedGraphQLTypes"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import { createTestChat } from "../../helpers/chat"; +import type { TestUserType } from "../../helpers/userAndOrg"; + +let testUser: TestUserType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestChat(); + testUser = resultArray[0]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> chatsByUserId", () => { + it(`throws NotFoundError if no Chats exists with chats.users + containing user with _id === args.id`, async () => { + try { + const args: QueryChatsByUserIdArgs = { + id: new Types.ObjectId().toString(), + }; + + await chatsByUserIdResolver?.({}, args, {}); + } catch (error: unknown) { + expect((error as Error).message).toEqual("Chats not found"); + } + }); + + it(`returns list of all chats with chat.users containing the user + with _id === args.id`, async () => { + const args: QueryChatsByUserIdArgs = { + id: testUser?._id, + }; + + const chatsByUserIdPayload = await chatsByUserIdResolver?.({}, args, {}); + + const chatsByUserId = await Chat.find({ + users: testUser?._id, + }).lean(); + + expect(chatsByUserIdPayload).toEqual(chatsByUserId); + }); +}); diff --git a/tests/resolvers/Query/checkAuth.spec.ts b/tests/resolvers/Query/checkAuth.spec.ts index a4539389b94..7024150f667 100644 --- a/tests/resolvers/Query/checkAuth.spec.ts +++ b/tests/resolvers/Query/checkAuth.spec.ts @@ -9,7 +9,7 @@ import { USER_NOT_FOUND_ERROR } from "../../../src/constants"; let MONGOOSE_INSTANCE: typeof mongoose; -import { AppUserProfile, User } from "../../../src/models"; +import { AppUserProfile } from "../../../src/models"; import { createTestUser } from "../../helpers/userAndOrg"; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); @@ -44,34 +44,6 @@ describe("resolvers -> Query -> checkAuth", () => { expect(user).toEqual({ ...testUser?.toObject(), image: null }); }); - it("returns user object with image ", async () => { - let testUser = await createTestUser(); - - await User.findOneAndUpdate( - { - _id: testUser?.id, - }, - { - image: `path`, - }, - ); - - testUser = await User.findOne({ - _id: testUser?.id, - }); - - const context = { - userId: testUser?._id, - apiRootUrl: `http://localhost:3000`, - }; - - const user = await checkAuthResolver?.({}, {}, context); - - expect(user).toEqual({ - ...testUser?.toObject(), - image: `${context.apiRootUrl}${testUser?.image}`, - }); - }); it("throws error if user does not have appUserProfile", async () => { try { const testUser = await createTestUser(); diff --git a/tests/resolvers/Query/directChatById.spec.ts b/tests/resolvers/Query/directChatById.spec.ts deleted file mode 100644 index 0b6e993df19..00000000000 --- a/tests/resolvers/Query/directChatById.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { connect, disconnect } from "../../helpers/db"; - -import { directChatById as directChatByIdResolver } from "../../../src/resolvers/Query/directChatById"; -import { DirectChat } from "../../../src/models"; -import type { QueryDirectChatsByUserIdArgs } from "../../../src/types/generatedGraphQLTypes"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { createTestDirectChat } from "../../helpers/directChat"; -import type { TestDirectChatType } from "../../helpers/directChat"; - -let testDirectChat: TestDirectChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestDirectChat(); - testDirectChat = resultArray[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Query -> directChatsById", () => { - it(`throws NotFoundError if no directChats exists with directChats._id === args.id`, async () => { - try { - const args: QueryDirectChatsByUserIdArgs = { - id: new Types.ObjectId().toString(), - }; - - await directChatByIdResolver?.({}, args, {}); - } catch (error: unknown) { - expect((error as Error).message).toEqual("Chat not found"); - } - }); - - it(`returns list of all directChats with directChat.users containing the user - with _id === args.id`, async () => { - const args: QueryDirectChatsByUserIdArgs = { - id: testDirectChat?._id, - }; - - const directChatsByUserIdPayload = await directChatByIdResolver?.( - {}, - args, - {}, - ); - - const directChatsByUserId = await DirectChat.findById( - testDirectChat?._id, - ).lean(); - console.log(directChatsByUserIdPayload); - console.log(directChatsByUserId); - expect(directChatsByUserIdPayload).toEqual(directChatsByUserId); - }); -}); diff --git a/tests/resolvers/Query/directChatsByUserID.spec.ts b/tests/resolvers/Query/directChatsByUserID.spec.ts deleted file mode 100644 index 780dd529f82..00000000000 --- a/tests/resolvers/Query/directChatsByUserID.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { connect, disconnect } from "../../helpers/db"; - -import { directChatsByUserID as directChatsByUserIDResolver } from "../../../src/resolvers/Query/directChatsByUserID"; -import { DirectChat } from "../../../src/models"; -import type { QueryDirectChatsByUserIdArgs } from "../../../src/types/generatedGraphQLTypes"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { createTestDirectChat } from "../../helpers/directChat"; -import type { TestUserType } from "../../helpers/userAndOrg"; - -let testUser: TestUserType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestDirectChat(); - testUser = resultArray[0]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Query -> directChatsByUserID", () => { - it(`throws NotFoundError if no directChats exists with directChats.users - containing user with _id === args.id`, async () => { - try { - const args: QueryDirectChatsByUserIdArgs = { - id: new Types.ObjectId().toString(), - }; - - await directChatsByUserIDResolver?.({}, args, {}); - } catch (error: unknown) { - expect((error as Error).message).toEqual("DirectChats not found"); - } - }); - - it(`returns list of all directChats with directChat.users containing the user - with _id === args.id`, async () => { - const args: QueryDirectChatsByUserIdArgs = { - id: testUser?._id, - }; - - const directChatsByUserIdPayload = await directChatsByUserIDResolver?.( - {}, - args, - {}, - ); - - const directChatsByUserId = await DirectChat.find({ - users: testUser?._id, - }).lean(); - - expect(directChatsByUserIdPayload).toEqual(directChatsByUserId); - }); -}); diff --git a/tests/resolvers/Query/directChatsMessagesByChatID.spec.ts b/tests/resolvers/Query/directChatsMessagesByChatID.spec.ts deleted file mode 100644 index 78a22efd447..00000000000 --- a/tests/resolvers/Query/directChatsMessagesByChatID.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import "dotenv/config"; -import { CHAT_NOT_FOUND_ERROR } from "../../../src/constants"; -import { directChatsMessagesByChatID as directChatsMessagesByChatIDResolver } from "../../../src/resolvers/Query/directChatsMessagesByChatID"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; - -import { DirectChatMessage } from "../../../src/models"; -import type { QueryDirectChatsMessagesByChatIdArgs } from "../../../src/types/generatedGraphQLTypes"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { - createTestUser, - createTestUserAndOrganization, -} from "../../helpers/userAndOrg"; -import type { TestDirectChatType } from "../../helpers/directChat"; -import { - createTestDirectChatwithUsers, - createDirectChatMessage, -} from "../../helpers/directChat"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testDirectChats: TestDirectChatType[]; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - - const [testUser1, testOrganization] = await createTestUserAndOrganization(); - const testUser2 = await createTestUser(); - - const testDirectChat1 = await createTestDirectChatwithUsers( - testUser1?._id, - testOrganization?._id, - [testUser1?._id, testUser2?._id], - ); - const testDirectChat2 = await createTestDirectChatwithUsers( - testUser2?._id, - testOrganization?._id, - [testUser2?._id], - ); - - testDirectChats = [testDirectChat1, testDirectChat2]; - await createDirectChatMessage( - testUser1?._id, - testUser2?._id, - testDirectChats[0]?._id.toString() || "", - ); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Query -> directChatsMessagesByChatID", () => { - it(`throws NotFoundError if no directChat exists with _id === args.id`, async () => { - try { - const args: QueryDirectChatsMessagesByChatIdArgs = { - id: new Types.ObjectId().toString(), - }; - - await directChatsMessagesByChatIDResolver?.({}, args, {}); - } catch (error: unknown) { - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.DESC); - } - }); - - it(`throws NotFoundError if no directChatMessages exist - for directChat with _id === args.id`, async () => { - try { - const args: QueryDirectChatsMessagesByChatIdArgs = { - id: testDirectChats[1]?._id.toString() || "", - }; - - await directChatsMessagesByChatIDResolver?.({}, args, {}); - } catch (error: unknown) { - expect((error as Error).message).toEqual(CHAT_NOT_FOUND_ERROR.DESC); - } - }); - - it(`returns list of all directChatMessages found - for directChat with _id === args.id`, async () => { - const args: QueryDirectChatsMessagesByChatIdArgs = { - id: testDirectChats[0]?._id.toString() || "", - }; - - const directChatsMessagesByChatIdPayload = - await directChatsMessagesByChatIDResolver?.({}, args, {}); - - const directChatMessagesByChatId = await DirectChatMessage.find({ - directChatMessageBelongsTo: testDirectChats[0]?._id, - }).lean(); - - expect(directChatsMessagesByChatIdPayload).toEqual( - directChatMessagesByChatId, - ); - }); -}); diff --git a/tests/resolvers/Query/eventVolunteersByEvent.spec.ts b/tests/resolvers/Query/eventVolunteersByEvent.spec.ts deleted file mode 100644 index 7c02d96f0ea..00000000000 --- a/tests/resolvers/Query/eventVolunteersByEvent.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type mongoose from "mongoose"; -import { connect, disconnect } from "../../helpers/db"; -import { eventVolunteersByEvent } from "../../../src/resolvers/Query/eventVolunteersByEvent"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { TestEventType } from "../../helpers/events"; -import { createTestEventAndVolunteer } from "../../helpers/events"; -import { EventVolunteer } from "../../../src/models"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testEvent: TestEventType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const temp = await createTestEventAndVolunteer(); - testEvent = temp[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Mutation -> eventVolunteersByEvent", () => { - it(`returns list of all existing event volunteers with eventId === args.id`, async () => { - const volunteersPayload = await eventVolunteersByEvent?.( - {}, - { - id: testEvent?._id, - }, - {}, - ); - - const volunteers = await EventVolunteer.find({ - eventId: testEvent?._id, - }) - .populate("userId", "-password") - .lean(); - - expect(volunteersPayload).toEqual(volunteers); - }); -}); diff --git a/tests/resolvers/Query/eventsAttendedByUser.spec.ts b/tests/resolvers/Query/eventsAttendedByUser.spec.ts new file mode 100644 index 00000000000..7ee28074174 --- /dev/null +++ b/tests/resolvers/Query/eventsAttendedByUser.spec.ts @@ -0,0 +1,62 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Event } from "../../../src/models"; +import { connect, disconnect } from "../../helpers/db"; +import type { QueryEventsAttendedByUserArgs } from "../../../src/types/generatedGraphQLTypes"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { + TestUserType, + TestOrganizationType, +} from "../../helpers/userAndOrg"; +import { createTestUserAndOrganization } from "../../helpers/userAndOrg"; +import { createEventWithRegistrant } from "../../helpers/events"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testUser: TestUserType; +let testOrganization: TestOrganizationType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [testUser, testOrganization] = await createTestUserAndOrganization(); + + await createEventWithRegistrant(testUser?._id, testOrganization?._id, true); + await createEventWithRegistrant(testUser?._id, testOrganization?._id, true); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> eventsAttendedByUser", () => { + it(`returns list of all events attended by user sorted by ascending order of event._id if args.orderBy === 'id_ASC'`, async () => { + const args: QueryEventsAttendedByUserArgs = { + id: testUser?._id, + orderBy: "id_ASC", + }; + + const { eventsAttendedByUser } = await import( + "../../../src/resolvers/Query/eventsAttendedByUser" + ); + + const eventsAttendedByUserPayload = await eventsAttendedByUser?.( + {}, + args, + {}, + ); + + const eventsAttendedByUserInfo = await Event.find({ + registrants: { + $elemMatch: { + userId: testUser?._id, + status: "ACTIVE", + }, + }, + }) + .sort({ _id: 1 }) + .populate("creatorId", "-password") + .populate("admins", "-password") + .lean(); + + expect(eventsAttendedByUserPayload).toEqual(eventsAttendedByUserInfo); + }); +}); diff --git a/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts b/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts index 674ad19e97f..15d1ce6492b 100644 --- a/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts +++ b/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts @@ -40,24 +40,50 @@ beforeAll(async () => { await dropAllCollectionsFromDatabase(MONGOOSE_INSTANCE); [testUser, testOrganization] = await createTestUserAndOrganization(); const testEvent1 = await createEventWithRegistrant( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - testUser!._id.toString(), + testUser?._id.toString() ?? "defaultUserId", testOrganization?._id, true, ); const testEvent2 = await createEventWithRegistrant( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - testUser!._id.toString(), + testUser?._id.toString() ?? "defaultUserId", testOrganization?._id, false, ); const testEvent3 = await createEventWithRegistrant( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - testUser!._id.toString(), + testUser?._id.toString() ?? "defaultUserId", testOrganization?._id, false, ); - testEvents = [testEvent1, testEvent2, testEvent3]; + const testEvent4 = await createEventWithRegistrant( + testUser?._id.toString() ?? "defaultUserId", + testOrganization?._id, + false, + ); + const testEvent5 = await createEventWithRegistrant( + testUser?._id.toString() ?? "defaultUserId", + testOrganization?._id, + false, + ); + + if (testEvent4) { + const today = new Date(); + const nextWeek = addDays(today, 7); + testEvent4.startDate = nextWeek.toISOString().split("T")[0]; + testEvent4.endDate = nextWeek.toISOString().split("T")[0]; + await testEvent4.save(); + } + + if (testEvent5) { + // set endDate to today and set endTime to 1 min from now + const today = new Date(); + testEvent5.endDate = today.toISOString().split("T")[0]; + testEvent5.endTime = new Date( + today.setMinutes(today.getMinutes() + 1), + ).toISOString(); + await testEvent5.save(); + } + + testEvents = [testEvent1, testEvent2, testEvent3, testEvent4, testEvent5]; }); afterAll(async () => { @@ -639,4 +665,19 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { vi.useRealTimers(); }); + + it("fetch upcoming events for the current date", async () => { + const upcomingEvents = (await eventsByOrganizationConnectionResolver?.( + {}, + { + upcomingOnly: true, + where: { + organization_id: testOrganization?._id, + }, + }, + {}, + )) as unknown as InterfaceEvent[]; + expect(upcomingEvents[0]?._id).toEqual(testEvents[3]?._id); + expect(upcomingEvents[1]?._id).toEqual(testEvents[4]?._id); + }); }); diff --git a/tests/resolvers/Query/getEventVolunteerGroups.spec.ts b/tests/resolvers/Query/getEventVolunteerGroups.spec.ts index a52a9a49d42..ab95e31c014 100644 --- a/tests/resolvers/Query/getEventVolunteerGroups.spec.ts +++ b/tests/resolvers/Query/getEventVolunteerGroups.spec.ts @@ -1,23 +1,52 @@ import type mongoose from "mongoose"; import { connect, disconnect } from "../../helpers/db"; -import { getEventVolunteerGroups } from "../../../src/resolvers/Query/getEventVolunteerGroups"; import { beforeAll, afterAll, describe, it, expect } from "vitest"; import type { TestEventType, TestEventVolunteerGroupType, + TestEventVolunteerType, } from "../../helpers/events"; -import { createTestEventVolunteerGroup } from "../../helpers/events"; -import type { EventVolunteerGroup } from "../../../src/types/generatedGraphQLTypes"; +import type { TestUserType } from "../../helpers/user"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import type { InterfaceEventVolunteerGroup } from "../../../src/models"; +import { EventVolunteer, EventVolunteerGroup } from "../../../src/models"; +import { getEventVolunteerGroups } from "../../../src/resolvers/Query/getEventVolunteerGroups"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; let MONGOOSE_INSTANCE: typeof mongoose; +let testOrganization: TestOrganizationType; let testEvent: TestEventType; -let testEventVolunteerGroup: TestEventVolunteerGroupType; +let testUser1: TestUserType; +let testEventVolunteer1: TestEventVolunteerType; +let testVolunteerGroup1: TestEventVolunteerGroupType; +let testVolunteerGroup2: TestEventVolunteerGroupType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const temp = await createTestEventVolunteerGroup(); - testEvent = temp[2]; - testEventVolunteerGroup = temp[4]; + const [organization, event, user1, , volunteer1, , volunteerGroup] = + await createVolunteerAndActions(); + + testOrganization = organization; + testEvent = event; + testUser1 = user1; + testEventVolunteer1 = volunteer1; + testVolunteerGroup1 = volunteerGroup; + testVolunteerGroup2 = await EventVolunteerGroup.create({ + creator: testUser1?._id, + event: testEvent?._id, + volunteers: [testEventVolunteer1?._id], + leader: testUser1?._id, + assignments: [], + name: "Test Volunteer Group 2", + }); + + await EventVolunteer.updateOne( + { _id: testEventVolunteer1?._id }, + { groups: [testVolunteerGroup1?._id, testVolunteerGroup2?._id] }, + { + new: true, + }, + ); }); afterAll(async () => { @@ -25,31 +54,77 @@ afterAll(async () => { }); describe("resolvers -> Query -> getEventVolunteerGroups", () => { - it(`returns list of all existing event volunteer groups with eventId === args.where.eventId`, async () => { - const volunteerGroupsPayload = (await getEventVolunteerGroups?.( + it(`getEventVolunteerGroups - eventId, name_contains, orderBy is volunteers_ASC`, async () => { + const groups = (await getEventVolunteerGroups?.( {}, { where: { eventId: testEvent?._id, + name_contains: testVolunteerGroup1.name, + leaderName: testUser1?.firstName, }, + orderBy: "volunteers_ASC", }, {}, - )) as unknown as EventVolunteerGroup[]; + )) as unknown as InterfaceEventVolunteerGroup[]; + + expect(groups[0].name).toEqual(testVolunteerGroup1.name); + }); + + it(`getEventVolunteerGroups - userId, orgId, orderBy is volunteers_DESC`, async () => { + const groups = (await getEventVolunteerGroups?.( + {}, + { + where: { + userId: testUser1?._id.toString(), + orgId: testOrganization?._id, + }, + orderBy: "volunteers_DESC", + }, + {}, + )) as unknown as InterfaceEventVolunteerGroup[]; + expect(groups.length).toEqual(2); + }); - expect(volunteerGroupsPayload[0]._id).toEqual(testEventVolunteerGroup._id); + it(`getEventVolunteerGroups - eventId, orderBy is assignments_ASC`, async () => { + const groups = (await getEventVolunteerGroups?.( + {}, + { + where: { + eventId: testEvent?._id, + }, + orderBy: "assignments_ASC", + }, + {}, + )) as unknown as InterfaceEventVolunteerGroup[]; + expect(groups[0].name).toEqual(testVolunteerGroup2.name); }); - it(`returns empty list of all existing event volunteer groups with eventId !== args.where.eventId`, async () => { - const volunteerGroupsPayload = (await getEventVolunteerGroups?.( + it(`getEventVolunteerGroups - eventId, orderBy is assignements_DESC`, async () => { + const groups = (await getEventVolunteerGroups?.( {}, { where: { - eventId: "123456789012345678901234", + eventId: testEvent?._id, }, + orderBy: "assignments_DESC", }, {}, - )) as unknown as EventVolunteerGroup[]; + )) as unknown as InterfaceEventVolunteerGroup[]; + expect(groups[0].name).toEqual(testVolunteerGroup1.name); + }); - expect(volunteerGroupsPayload).toEqual([]); + it(`getEventVolunteerGroups - userId, wrong orgId`, async () => { + const groups = (await getEventVolunteerGroups?.( + {}, + { + where: { + userId: testUser1?._id.toString(), + orgId: testEvent?._id, + }, + }, + {}, + )) as unknown as InterfaceEventVolunteerGroup[]; + expect(groups).toEqual([]); }); }); diff --git a/tests/resolvers/Query/getEventVolunteers.spec.ts b/tests/resolvers/Query/getEventVolunteers.spec.ts new file mode 100644 index 00000000000..93c532c0d3b --- /dev/null +++ b/tests/resolvers/Query/getEventVolunteers.spec.ts @@ -0,0 +1,62 @@ +import type mongoose from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { + TestEventType, + TestEventVolunteerGroupType, + TestEventVolunteerType, +} from "../../helpers/events"; +import type { TestUserType } from "../../helpers/user"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import type { InterfaceEventVolunteer } from "../../../src/models"; +import { getEventVolunteers } from "../../../src/resolvers/Query/getEventVolunteers"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testEvent: TestEventType; +let testUser1: TestUserType; +let testEventVolunteer1: TestEventVolunteerType; +let testVolunteerGroup: TestEventVolunteerGroupType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const [, event, user1, , volunteer1, , volunteerGroup, ,] = + await createVolunteerAndActions(); + + testEvent = event; + testUser1 = user1; + testEventVolunteer1 = volunteer1; + testVolunteerGroup = volunteerGroup; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> getEventVolunteers", () => { + it(`getEventVolunteers - eventId, name_contains`, async () => { + const eventVolunteers = (await getEventVolunteers?.( + {}, + { + where: { + eventId: testEvent?._id, + name_contains: testUser1?.firstName, + }, + }, + {}, + )) as unknown as InterfaceEventVolunteer[]; + expect(eventVolunteers[0].user.firstName).toEqual(testUser1?.firstName); + }); + it(`getEventVolunteers - eventId, groupId`, async () => { + const eventVolunteers = (await getEventVolunteers?.( + {}, + { + where: { + eventId: testEvent?._id, + groupId: testVolunteerGroup?._id, + }, + }, + {}, + )) as unknown as InterfaceEventVolunteer[]; + expect(eventVolunteers[0]._id).toEqual(testEventVolunteer1?._id); + }); +}); diff --git a/tests/resolvers/Query/getFile.spec.ts b/tests/resolvers/Query/getFile.spec.ts new file mode 100644 index 00000000000..a6ff41d4f43 --- /dev/null +++ b/tests/resolvers/Query/getFile.spec.ts @@ -0,0 +1,138 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { GetObjectCommandOutput } from "@aws-sdk/client-s3"; +import { GetObjectCommand } from "@aws-sdk/client-s3"; +import type { Request, Response } from "express"; +import type { Readable } from "stream"; +import { s3Client } from "../../../src/config/minio"; +import { getFile } from "../../../src/REST/controllers/query"; +import type { SdkStreamMixin } from "@aws-sdk/types"; + +// Mock the s3Client +vi.mock("../../../src/config/minio", () => ({ + s3Client: { + send: vi.fn(), + }, + BUCKET_NAME: "test-bucket", +})); + +describe("getFile", () => { + let mockRequest: Request; + let mockResponse: Response; + + beforeEach(() => { + vi.clearAllMocks(); + + mockResponse = { + setHeader: vi.fn(), + status: vi.fn().mockReturnThis(), + send: vi.fn(), + pipe: vi.fn(), + } as unknown as Response; + + mockRequest = { + params: { + "0": "test-file.txt", + }, + } as unknown as Request; + }); + + const createMockStream = (): Readable & SdkStreamMixin => { + // Create a minimal mock that implements only what we need + const mockStream = { + pipe: vi.fn().mockReturnThis(), + on: vi.fn().mockReturnThis(), + once: vi.fn().mockReturnThis(), + emit: vi.fn().mockReturnThis(), + transformToByteArray: (): Promise => + Promise.resolve(new Uint8Array()), + transformToWebStream: (): ReadableStream => new ReadableStream(), + }; + + return mockStream as unknown as Readable & SdkStreamMixin; + }; + + it("should successfully retrieve and stream a file", async () => { + const mockStream = createMockStream(); + + const send = vi.fn().mockImplementation(() => + Promise.resolve({ + Body: mockStream, + ContentType: "text/plain", + $metadata: {}, + } as GetObjectCommandOutput), + ); + + vi.mocked(s3Client.send).mockImplementation(send); + + await getFile(mockRequest, mockResponse); + + expect(s3Client.send).toHaveBeenCalledWith(expect.any(GetObjectCommand)); + + const commandCall = vi.mocked(s3Client.send).mock + .calls[0][0] as GetObjectCommand; + expect(commandCall.input).toEqual({ + Bucket: "test-bucket", + Key: "test-file.txt", + }); + + expect(mockResponse.setHeader).toHaveBeenCalledWith( + "Content-Type", + "text/plain", + ); + expect(mockResponse.setHeader).toHaveBeenCalledWith( + "Cross-Origin-Resource-Policy", + "same-site", + ); + + expect(mockStream.pipe).toHaveBeenCalledWith(mockResponse); + }); + + it("should handle errors when retrieving file fails", async () => { + const mockError = new Error("Failed to retrieve file"); + const send = vi.fn().mockImplementation(() => Promise.reject(mockError)); + vi.mocked(s3Client.send).mockImplementation(send); + + const consoleErrorSpy = vi.spyOn(console, "error"); + + await getFile(mockRequest, mockResponse); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Error fetching file:", + mockError, + ); + + expect(mockResponse.status).toHaveBeenCalledWith(500); + expect(mockResponse.send).toHaveBeenCalledWith( + "Error occurred while fetching file", + ); + }); + + it("should handle file paths with subdirectories", async () => { + const mockRequestWithPath = { + params: { + "0": "subfolder/nested/test-file.txt", + }, + } as unknown as Request; + + const mockStream = createMockStream(); + + const send = vi.fn().mockImplementation(() => + Promise.resolve({ + Body: mockStream, + ContentType: "text/plain", + $metadata: {}, + } as GetObjectCommandOutput), + ); + + vi.mocked(s3Client.send).mockImplementation(send); + + await getFile(mockRequestWithPath, mockResponse); + + const commandCall = vi.mocked(s3Client.send).mock + .calls[0][0] as GetObjectCommand; + expect(commandCall.input).toEqual({ + Bucket: "test-bucket", + Key: "subfolder/nested/test-file.txt", + }); + }); +}); diff --git a/tests/resolvers/Query/getRecurringEvents.spec.ts b/tests/resolvers/Query/getRecurringEvents.spec.ts new file mode 100644 index 00000000000..8b995ca4b3c --- /dev/null +++ b/tests/resolvers/Query/getRecurringEvents.spec.ts @@ -0,0 +1,118 @@ +import "dotenv/config"; +import { getRecurringEvents } from "../../../src/resolvers/Query/getRecurringEvents"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { Event } from "../../../src/models"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import { Types } from "mongoose"; +import { + createTestUser, + createTestUserAndOrganization, + type TestOrganizationType, + type TestUserType, +} from "../../helpers/userAndOrg"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testUser: TestUserType; +let testAdminUser: TestUserType; +let testOrganization: TestOrganizationType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const result = await createTestUserAndOrganization(); + testUser = result[0]; + testOrganization = result[1]; + testAdminUser = await createTestUser(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> getRecurringEvents", () => { + it("returns list of recurring events for a given baseRecurringEventId", async () => { + if (!testUser || !testAdminUser || !testOrganization) { + throw new Error("Test setup failed"); + } + const baseRecurringEventId = new Types.ObjectId(); + + const testEvents = [ + { + title: "Event 1", + description: "description", + allDay: true, + startDate: new Date().toUTCString(), + recurring: true, + isPublic: true, + isRegisterable: true, + creatorId: testUser._id, + admins: [testAdminUser._id], + registrants: [], + organization: testOrganization._id, + baseRecurringEventId, + }, + { + title: "Event 2", + description: "description", + allDay: true, + startDate: new Date(), + recurring: true, + isPublic: true, + isRegisterable: true, + creatorId: testUser._id, + admins: [testAdminUser._id], + registrants: [], + organization: testOrganization._id, + baseRecurringEventId, + }, + ]; + + await Event.insertMany(testEvents); + + const args = { baseRecurringEventId: baseRecurringEventId.toString() }; + const getRecurringEventsFunction = getRecurringEvents as unknown as ( + parent: any, + args: any, + context: any, + ) => Promise; + const result = await getRecurringEventsFunction({}, args, {}); + + expect(result).toHaveLength(2); + expect(result[0].title).toBe("Event 1"); + expect(result[1].title).toBe("Event 2"); + + await Event.deleteMany({ baseRecurringEventId }); + }); + + it("returns an empty array when no recurring events are found", async () => { + const nonExistentId = new Types.ObjectId(); + const args = { baseRecurringEventId: nonExistentId.toString() }; + const getRecurringEventsFunction = getRecurringEvents as unknown as ( + parent: any, + args: any, + context: any, + ) => Promise; + const result = await getRecurringEventsFunction({}, args, {}); + + expect(result).toEqual([]); + }); + + it("throws an error when there's a problem fetching events", async () => { + const mockFind = vi.spyOn(Event, "find").mockImplementation(() => { + throw new Error("Database error"); + }); + + const args = { baseRecurringEventId: new Types.ObjectId().toString() }; + const getRecurringEventsFunction = getRecurringEvents as unknown as ( + parent: any, + args: any, + context: any, + ) => Promise; + + await expect(getRecurringEventsFunction({}, args, {})).rejects.toThrow( + "Database error", + ); + + mockFind.mockRestore(); + }); +}); diff --git a/tests/resolvers/Query/getUserTag.spec.ts b/tests/resolvers/Query/getUserTag.spec.ts index f81f51d9607..0e935a383a7 100644 --- a/tests/resolvers/Query/getUserTag.spec.ts +++ b/tests/resolvers/Query/getUserTag.spec.ts @@ -22,7 +22,7 @@ afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers -> Query -> getUserTagAncestors", () => { +describe("resolvers -> Query -> getUserTag", () => { it(`throws NotFoundError if no userTag exists with _id === args.id`, async () => { const { requestContext } = await import("../../../src/libraries"); @@ -49,8 +49,8 @@ describe("resolvers -> Query -> getUserTagAncestors", () => { id: testTag?._id.toString() ?? "", }; - const getUserTagAncestorsPayload = await getUserTagResolver?.({}, args, {}); + const getUserTagPayload = await getUserTagResolver?.({}, args, {}); - expect(getUserTagAncestorsPayload).toEqual(testTag); + expect(getUserTagPayload).toEqual(testTag); }); }); diff --git a/tests/resolvers/Query/getUserTagAncestors.spec.ts b/tests/resolvers/Query/getUserTagAncestors.spec.ts deleted file mode 100644 index 2a3ee00143a..00000000000 --- a/tests/resolvers/Query/getUserTagAncestors.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { connect, disconnect } from "../../helpers/db"; - -import { getUserTagAncestors as getUserTagAncestorsResolver } from "../../../src/resolvers/Query/getUserTagAncestors"; -import type { QueryGetUserTagAncestorsArgs } from "../../../src/types/generatedGraphQLTypes"; -import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; -import { - createTwoLevelTagsWithOrg, - type TestUserTagType, -} from "../../helpers/tags"; -import { TAG_NOT_FOUND } from "../../../src/constants"; - -let MONGOOSE_INSTANCE: typeof mongoose; - -let testTag: TestUserTagType; -let testSubTag1: TestUserTagType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [, , [testTag, testSubTag1]] = await createTwoLevelTagsWithOrg(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Query -> getUserTagAncestors", () => { - it(`throws NotFoundError if no userTag exists with _id === args.id`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const args: QueryGetUserTagAncestorsArgs = { - id: new Types.ObjectId().toString(), - }; - - await getUserTagAncestorsResolver?.({}, args, {}); - } catch (error: unknown) { - expect(spy).toHaveBeenLastCalledWith(TAG_NOT_FOUND.MESSAGE); - expect((error as Error).message).toEqual( - `Translated ${TAG_NOT_FOUND.MESSAGE}`, - ); - } - }); - - it(`returns the list of all the ancestor tags for a tag with _id === args.id`, async () => { - const args: QueryGetUserTagAncestorsArgs = { - id: testSubTag1?._id.toString() ?? "", - }; - - const getUserTagAncestorsPayload = await getUserTagAncestorsResolver?.( - {}, - args, - {}, - ); - - expect(getUserTagAncestorsPayload).toEqual([testTag, testSubTag1]); - }); -}); diff --git a/tests/resolvers/Query/getVolunteerMembership.spec.ts b/tests/resolvers/Query/getVolunteerMembership.spec.ts new file mode 100644 index 00000000000..ecc68c5d3a9 --- /dev/null +++ b/tests/resolvers/Query/getVolunteerMembership.spec.ts @@ -0,0 +1,170 @@ +import type mongoose from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { + TestEventType, + TestEventVolunteerGroupType, + TestEventVolunteerType, +} from "../../helpers/events"; +import type { TestUserType } from "../../helpers/user"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import type { InterfaceVolunteerMembership } from "../../../src/models"; +import { VolunteerMembership } from "../../../src/models"; +import { getVolunteerMembership } from "../../../src/resolvers/Query/getVolunteerMembership"; + +export enum MembershipStatus { + INVITED = "invited", + REQUESTED = "requested", + ACCEPTED = "accepted", + REJECTED = "rejected", +} + +let MONGOOSE_INSTANCE: typeof mongoose; +let testEvent: TestEventType; +let testUser1: TestUserType; +let testEventVolunteer1: TestEventVolunteerType; +let testEventVolunteerGroup: TestEventVolunteerGroupType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const [, event, user1, , volunteer1, , volunteerGroup, ,] = + await createVolunteerAndActions(); + + testEvent = event; + testUser1 = user1; + testEventVolunteer1 = volunteer1; + testEventVolunteerGroup = volunteerGroup; + + await VolunteerMembership.insertMany([ + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + status: MembershipStatus.INVITED, + }, + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + group: testEventVolunteerGroup._id, + status: MembershipStatus.REQUESTED, + }, + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + status: MembershipStatus.ACCEPTED, + }, + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + status: MembershipStatus.REJECTED, + }, + { + event: testEvent?._id, + volunteer: testEventVolunteer1._id, + status: MembershipStatus.INVITED, + group: testEventVolunteerGroup._id, + }, + ]); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> getVolunteerMembership", () => { + it(`getVolunteerMembership for userId, status invited`, async () => { + const volunteerMemberships = (await getVolunteerMembership?.( + {}, + { + where: { + userId: testUser1?._id.toString(), + status: MembershipStatus.INVITED, + }, + }, + {}, + )) as unknown as InterfaceVolunteerMembership[]; + expect(volunteerMemberships[0].volunteer?._id).toEqual( + testEventVolunteer1?._id, + ); + expect(volunteerMemberships[0].status).toEqual(MembershipStatus.INVITED); + }); + + it(`getVolunteerMembership for eventId, status accepted`, async () => { + const volunteerMemberships = (await getVolunteerMembership?.( + {}, + { + where: { + eventId: testEvent?._id, + eventTitle: testEvent?.title, + status: MembershipStatus.ACCEPTED, + }, + orderBy: "createdAt_ASC", + }, + {}, + )) as unknown as InterfaceVolunteerMembership[]; + expect(volunteerMemberships[0].volunteer?._id).toEqual( + testEventVolunteer1?._id, + ); + expect(volunteerMemberships[0].status).toEqual(MembershipStatus.ACCEPTED); + expect(volunteerMemberships[0].event._id).toEqual(testEvent?._id); + }); + + it(`getVolunteerMembership for eventId, filter group, userName`, async () => { + const volunteerMemberships = (await getVolunteerMembership?.( + {}, + { + where: { + eventId: testEvent?._id, + filter: "group", + userName: testUser1?.firstName, + }, + }, + {}, + )) as unknown as InterfaceVolunteerMembership[]; + expect(volunteerMemberships[0].volunteer?._id).toEqual( + testEventVolunteer1?._id, + ); + expect(volunteerMemberships[0].status).toEqual(MembershipStatus.REQUESTED); + expect(volunteerMemberships[0].event._id).toEqual(testEvent?._id); + expect(volunteerMemberships[0].group?._id).toEqual( + testEventVolunteerGroup?._id, + ); + }); + + it(`getVolunteerMembership for userId`, async () => { + const volunteerMemberships = (await getVolunteerMembership?.( + {}, + { + where: { + userId: testUser1?._id.toString(), + filter: "individual", + }, + }, + {}, + )) as unknown as InterfaceVolunteerMembership[]; + expect(volunteerMemberships.length).toEqual(3); + expect(volunteerMemberships[0].group).toBeUndefined(); + expect(volunteerMemberships[1].group).toBeUndefined(); + expect(volunteerMemberships[2].group).toBeUndefined(); + }); + + it(`getVolunteerMembership for eventId, groupId`, async () => { + const volunteerMemberships = (await getVolunteerMembership?.( + {}, + { + where: { + eventId: testEvent?._id, + groupId: testEventVolunteerGroup?._id, + filter: "group", + userName: testUser1?.firstName, + }, + }, + {}, + )) as unknown as InterfaceVolunteerMembership[]; + expect(volunteerMemberships[0].volunteer?._id).toEqual( + testEventVolunteer1?._id, + ); + expect(volunteerMemberships[0].group?._id).toEqual( + testEventVolunteerGroup?._id, + ); + }); +}); diff --git a/tests/resolvers/Query/getVolunteerRanks.spec.ts b/tests/resolvers/Query/getVolunteerRanks.spec.ts new file mode 100644 index 00000000000..0f5d6973d5c --- /dev/null +++ b/tests/resolvers/Query/getVolunteerRanks.spec.ts @@ -0,0 +1,100 @@ +import type mongoose from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { VolunteerRank } from "../../../src/types/generatedGraphQLTypes"; +import type { TestUserType } from "../../helpers/user"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; +import { createVolunteerAndActions } from "../../helpers/volunteers"; +import { getVolunteerRanks } from "../../../src/resolvers/Query/getVolunteerRanks"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testOrganization: TestOrganizationType; +let testUser1: TestUserType; +let testUser2: TestUserType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const [organization, , user1, user2] = await createVolunteerAndActions(); + testOrganization = organization; + testUser1 = user1; + testUser2 = user2; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> getVolunteerRanks", () => { + it(`getVolunteerRanks for allTime, descending, no limit/name`, async () => { + const volunteerRanks = (await getVolunteerRanks?.( + {}, + { + orgId: testOrganization?._id, + where: { + timeFrame: "allTime", + orderBy: "hours_DESC", + }, + }, + {}, + )) as unknown as VolunteerRank[]; + expect(volunteerRanks[0].hoursVolunteered).toEqual(10); + expect(volunteerRanks[0].user._id).toEqual(testUser1?._id); + expect(volunteerRanks[0].rank).toEqual(1); + expect(volunteerRanks[1].hoursVolunteered).toEqual(8); + expect(volunteerRanks[1].user._id).toEqual(testUser2?._id); + expect(volunteerRanks[1].rank).toEqual(2); + }); + + it(`getVolunteerRanks for weekly, descending, limit, no name`, async () => { + const volunteerRanks = (await getVolunteerRanks?.( + {}, + { + orgId: testOrganization?._id, + where: { + timeFrame: "weekly", + orderBy: "hours_DESC", + limit: 1, + }, + }, + {}, + )) as unknown as VolunteerRank[]; + expect(volunteerRanks[0].hoursVolunteered).toEqual(2); + expect(volunteerRanks[0].user._id).toEqual(testUser1?._id); + expect(volunteerRanks[0].rank).toEqual(1); + }); + + it(`getVolunteerRanks for monthly, descending, name, no limit`, async () => { + const volunteerRanks = (await getVolunteerRanks?.( + {}, + { + orgId: testOrganization?._id, + where: { + timeFrame: "monthly", + orderBy: "hours_ASC", + nameContains: testUser1?.firstName, + }, + }, + {}, + )) as unknown as VolunteerRank[]; + expect(volunteerRanks[0].hoursVolunteered).toEqual(2); + expect(volunteerRanks[0].user._id).toEqual(testUser1?._id); + expect(volunteerRanks[0].rank).toEqual(1); + }); + + it(`getVolunteerRanks for yearly, descending, no name/limit`, async () => { + const volunteerRanks = (await getVolunteerRanks?.( + {}, + { + orgId: testOrganization?._id, + where: { + timeFrame: "yearly", + orderBy: "hours_DESC", + }, + }, + {}, + )) as unknown as VolunteerRank[]; + expect(volunteerRanks[0].hoursVolunteered).toEqual(8); + expect(volunteerRanks[0].user._id).toEqual(testUser1?._id); + expect(volunteerRanks[0].rank).toEqual(1); + }); +}); diff --git a/tests/resolvers/Query/groupChatById.spec.ts b/tests/resolvers/Query/groupChatById.spec.ts deleted file mode 100644 index 770557a3017..00000000000 --- a/tests/resolvers/Query/groupChatById.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { connect, disconnect } from "../../helpers/db"; - -import { groupChatById as groupChatByIdResolver } from "../../../src/resolvers/Query/groupChatById"; -import { GroupChat } from "../../../src/models"; -import type { - QueryGroupChatByIdArgs, - QueryGroupChatsByUserIdArgs, -} from "../../../src/types/generatedGraphQLTypes"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { createTestGroupChat } from "../../helpers/groupChat"; -import type { TestGroupChatType } from "../../helpers/groupChat"; - -let testGroupChat: TestGroupChatType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChat(); - testGroupChat = resultArray[2]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Query -> directChatsById", () => { - it(`throws NotFoundError if no directChats exists with directChats._id === args.id`, async () => { - try { - const args: QueryGroupChatByIdArgs = { - id: new Types.ObjectId().toString(), - }; - - await groupChatByIdResolver?.({}, args, {}); - } catch (error: unknown) { - expect((error as Error).message).toEqual("Chat not found"); - } - }); - - it(`returns list of all directChats with directChat.users containing the user - with _id === args.id`, async () => { - const args: QueryGroupChatsByUserIdArgs = { - id: testGroupChat?._id, - }; - - const directChatsByUserIdPayload = await groupChatByIdResolver?.( - {}, - args, - {}, - ); - - const directChatsByUserId = await GroupChat.findById( - testGroupChat?._id, - ).lean(); - console.log(directChatsByUserIdPayload); - console.log(directChatsByUserId); - expect(directChatsByUserIdPayload).toEqual(directChatsByUserId); - }); -}); diff --git a/tests/resolvers/Query/groupChatsByUserId.spec.ts b/tests/resolvers/Query/groupChatsByUserId.spec.ts deleted file mode 100644 index 7026580b172..00000000000 --- a/tests/resolvers/Query/groupChatsByUserId.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import "dotenv/config"; -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { connect, disconnect } from "../../helpers/db"; - -import { groupChatsByUserId as groupChatsByUserIdResolver } from "../../../src/resolvers/Query/groupChatsByUserId"; -import { GroupChat } from "../../../src/models"; -import type { QueryGroupChatsByUserIdArgs } from "../../../src/types/generatedGraphQLTypes"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { createTestGroupChat } from "../../helpers/groupChat"; -import type { TestUserType } from "../../helpers/userAndOrg"; - -let testUser: TestUserType; -let MONGOOSE_INSTANCE: typeof mongoose; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestGroupChat(); - testUser = resultArray[0]; -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> Query -> groupChatsByUserId", () => { - it(`throws NotFoundError if no groupChats exists with groupChats.users - containing user with _id === args.id`, async () => { - try { - const args: QueryGroupChatsByUserIdArgs = { - id: new Types.ObjectId().toString(), - }; - - await groupChatsByUserIdResolver?.({}, args, {}); - } catch (error: unknown) { - expect((error as Error).message).toEqual("Group Chats not found"); - } - }); - - it(`returns list of all groupChats with groupChat.users containing the user - with _id === args.id`, async () => { - const args: QueryGroupChatsByUserIdArgs = { - id: testUser?._id, - }; - - const groupChatsByUserIdPayload = await groupChatsByUserIdResolver?.( - {}, - args, - {}, - ); - - const groupChatsByUserId = await GroupChat.find({ - users: testUser?._id, - }).lean(); - - expect(groupChatsByUserIdPayload).toEqual(groupChatsByUserId); - }); -}); diff --git a/tests/resolvers/Query/helperFunctions/getSort.spec.ts b/tests/resolvers/Query/helperFunctions/getSort.spec.ts index 10b0b1668ca..98c5ca94340 100644 --- a/tests/resolvers/Query/helperFunctions/getSort.spec.ts +++ b/tests/resolvers/Query/helperFunctions/getSort.spec.ts @@ -9,6 +9,9 @@ import type { VenueOrderByInput, FundOrderByInput, CampaignOrderByInput, + ActionItemsOrderByInput, + EventVolunteersOrderByInput, + VolunteerMembershipOrderByInput, } from "../../../../src/types/generatedGraphQLTypes"; describe("getSort function", () => { @@ -61,6 +64,8 @@ describe("getSort function", () => { ["fundingGoal_DESC", { fundingGoal: -1 }], ["dueDate_ASC", { dueDate: 1 }], ["dueDate_DESC", { dueDate: -1 }], + ["hoursVolunteered_ASC", { hoursVolunteered: 1 }], + ["hoursVolunteered_DESC", { hoursVolunteered: -1 }], ]; it.each(testCases)( @@ -75,7 +80,10 @@ describe("getSort function", () => { | VenueOrderByInput | PledgeOrderByInput | FundOrderByInput - | CampaignOrderByInput, + | CampaignOrderByInput + | ActionItemsOrderByInput + | EventVolunteersOrderByInput + | VolunteerMembershipOrderByInput, ); expect(result).toEqual(expected); }, diff --git a/tests/resolvers/Query/helperFunctions/getWhere.spec.ts b/tests/resolvers/Query/helperFunctions/getWhere.spec.ts index 3c3494c9b6a..b6e8c12faa8 100644 --- a/tests/resolvers/Query/helperFunctions/getWhere.spec.ts +++ b/tests/resolvers/Query/helperFunctions/getWhere.spec.ts @@ -13,6 +13,7 @@ import type { EventVolunteerGroupWhereInput, PledgeWhereInput, ActionItemCategoryWhereInput, + EventVolunteerWhereInput, } from "../../../../src/types/generatedGraphQLTypes"; describe("getWhere function", () => { @@ -30,7 +31,8 @@ describe("getWhere function", () => { FundWhereInput & CampaignWhereInput & VenueWhereInput & - PledgeWhereInput + PledgeWhereInput & + EventVolunteerWhereInput >, Record, ][] = [ @@ -335,17 +337,10 @@ describe("getWhere function", () => { { organizationId: "6f6cd" }, ], ["campaignId", { campaignId: "6f6c" }, { _id: "6f6c" }], - [ - "volunteerId", - { volunteerId: "6f43d" }, - { - volunteers: { - $in: ["6f43d"], - }, - }, - ], ["is_disabled", { is_disabled: true }, { isDisabled: true }], ["is_disabled", { is_disabled: false }, { isDisabled: false }], + ["hasAccepted", { hasAccepted: true }, { hasAccepted: true }], + ["hasAccepted", { hasAccepted: false }, { hasAccepted: false }], ]; it.each(testCases)( diff --git a/tests/resolvers/Query/organizationsMemberConnection.spec.ts b/tests/resolvers/Query/organizationsMemberConnection.spec.ts index 2ce491dc822..9265454acf8 100644 --- a/tests/resolvers/Query/organizationsMemberConnection.spec.ts +++ b/tests/resolvers/Query/organizationsMemberConnection.spec.ts @@ -227,6 +227,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -247,6 +248,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -319,6 +321,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -339,6 +342,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); // console.log(organizationsMemberConnectionPayload, usersWithPassword); @@ -410,6 +414,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -430,6 +435,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -504,6 +510,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -524,6 +531,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -598,6 +606,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -618,6 +627,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -680,6 +690,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -700,6 +711,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -861,6 +873,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -881,6 +894,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -932,6 +946,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -952,6 +967,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -1032,6 +1048,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const users = usersTestModel.docs.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -1052,6 +1069,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); @@ -1076,7 +1094,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { {}, { $set: { - image: `image/image.png`, + image: BASE_URL + `image/image.png`, }, }, ); @@ -1113,6 +1131,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const usersWithPassword = users.map((user) => { return { _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: user.address, birthDate: user.birthDate, @@ -1122,7 +1141,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { employmentStatus: user.employmentStatus, firstName: user.firstName, gender: user.gender, - image: `${BASE_URL}${user.image}`, + image: user.image, joinedOrganizations: user.joinedOrganizations, lastName: user.lastName, maritalStatus: user.maritalStatus, @@ -1133,6 +1152,7 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { registeredEvents: user.registeredEvents, status: user.status, updatedAt: user.updatedAt, + eventsAttended: user.eventsAttended, }; }); diff --git a/tests/resolvers/Query/post.spec.ts b/tests/resolvers/Query/post.spec.ts index 7d6c1afe9ef..4f89104ec34 100644 --- a/tests/resolvers/Query/post.spec.ts +++ b/tests/resolvers/Query/post.spec.ts @@ -51,14 +51,9 @@ describe("resolvers -> Query -> post", () => { const post = await Post.findOne({ _id: testPost?._id }) .populate("organization") - .populate("likedBy") + .populate(["likedBy", "file"]) .lean(); - if (post) { - post.imageUrl = `${context.apiRootUrl}${post.imageUrl}`; - post.videoUrl = `${context.apiRootUrl}${post.videoUrl}`; - } - expect(postPayload).toEqual(post); }); it(`returns post object with null image and video links`, async () => { @@ -74,14 +69,9 @@ describe("resolvers -> Query -> post", () => { const post = await Post.findOne({ _id: testPost?._id }) .populate("organization") - .populate("likedBy") + .populate(["likedBy", "file"]) .lean(); - if (post) { - post.imageUrl = null; - post.videoUrl = null; - } - expect(postPayload).toEqual(post); }); }); diff --git a/tests/resolvers/Query/user.spec.ts b/tests/resolvers/Query/user.spec.ts index 3d00aebbc67..23f2c090ccc 100644 --- a/tests/resolvers/Query/user.spec.ts +++ b/tests/resolvers/Query/user.spec.ts @@ -1,7 +1,7 @@ import "dotenv/config"; import type mongoose from "mongoose"; import { Types } from "mongoose"; -import { BASE_URL, USER_NOT_FOUND_ERROR } from "../../../src/constants"; +import { USER_NOT_FOUND_ERROR } from "../../../src/constants"; import { User } from "../../../src/models"; import { user as userResolver } from "../../../src/resolvers/Query/user"; import { connect, disconnect } from "../../helpers/db"; @@ -64,37 +64,4 @@ describe("resolvers -> Query -> user", () => { image: null, }); }); - it(`returns user object with image`, async () => { - await User.findOneAndUpdate( - { - _id: testUser?.id, - }, - { - $set: { - image: `images/newImage.png`, - }, - }, - ); - - const args: QueryUserArgs = { - id: testUser?.id, - }; - - const context = { - userId: testUser?.id, - apiRootUrl: BASE_URL, - }; - - const userPayload = await userResolver?.({}, args, context); - - const user = await User.findOne({ - _id: testUser?._id, - }).lean(); - - expect(userPayload?.user).toEqual({ - ...user, - organizationsBlockedBy: [], - image: user?.image ? `${BASE_URL}${user.image}` : null, - }); - }); }); diff --git a/tests/resolvers/Subscription/directMessageChat.spec.ts b/tests/resolvers/Subscription/directMessageChat.spec.ts deleted file mode 100644 index 599ad5e9378..00000000000 --- a/tests/resolvers/Subscription/directMessageChat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import "dotenv/config"; -import { describe, it, expect } from "vitest"; - -describe("src -> resolvers -> Subscription -> directMessageChat", () => { - it("should return payload", async () => { - const { directMessageChat: directMessageChatPayload } = await import( - "../../../src/resolvers/Subscription/directMessageChat" - ); - const _args = {}; - const _parent = {}; - const context = { - pubsub: { - asyncIterator: (chatChannel: string): string => { - return chatChannel; - }, - }, - }; - // @ts-expect-error-ignore - const x = directMessageChatPayload?.subscribe(_parent, _args, context); - expect(x).not.toBe(null); - }); -}); diff --git a/tests/resolvers/Subscription/messageSentToChat.spec.ts b/tests/resolvers/Subscription/messageSentToChat.spec.ts new file mode 100644 index 00000000000..768b0f5c47b --- /dev/null +++ b/tests/resolvers/Subscription/messageSentToChat.spec.ts @@ -0,0 +1,88 @@ +import "dotenv/config"; +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import type { TestChatMessageType } from "../../helpers/chat"; +import { createTestChatMessage } from "../../helpers/chat"; +import type { TestUserType } from "../../helpers/userAndOrg"; +import { filterFunction } from "../../../src/resolvers/Subscription/messageSentToChat"; +import { Types } from "mongoose"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testChatMessage: TestChatMessageType; +let testCurrentUser: TestUserType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestChatMessage(); + testCurrentUser = resultArray[0]; + testChatMessage = resultArray[3]; +}); +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("src -> resolvers -> Subscription -> messageSentToChat", () => { + it("subscription filter function returns true if CurrentUser is receiveror sender", async () => { + const { messageSentToChat: messageSentToChatPayload } = await import( + "../../../src/resolvers/Subscription/messageSentToChat" + ); + + const _args = {}; + const _parent = {}; + const context = { + pubsub: { + asyncIterator: (_action: "MESSAGE_SENT_TO_CHAT"): string => { + return _action; + }, + }, + context: { currentUserId: testCurrentUser?._id }, + }; + const variables = { + userId: testCurrentUser?._id, + }; + const payload = { + messageSentToChat: { + chatMessageBelongsTo: testChatMessage?.chatMessageBelongsTo as string, + }, + }; + // @ts-expect-error-ignore + messageSentToChatPayload.payload = payload; + // @ts-expect-error-ignore + const x = messageSentToChatPayload?.subscribe(_parent, _args, context); + expect(x).not.toBe(null); + expect(await filterFunction(payload, variables)).toBe(true); + }); + + it("user is not notified if it is not a part of chat", async () => { + const { messageSentToChat: messageSentToChatPayload } = await import( + "../../../src/resolvers/Subscription/messageSentToChat" + ); + + const _args = {}; + const _parent = {}; + const context = { + pubsub: { + asyncIterator: (_action: "MESSAGE_SENT_TO_CHAT"): string => { + return _action; + }, + }, + context: { currentUserId: testCurrentUser?._id }, + }; + const variables = { + userId: testCurrentUser?._id, + }; + + const payload = { + messageSentToChat: { + chatMessageBelongsTo: new Types.ObjectId().toString(), + }, + }; + // @ts-expect-error-ignore + messageSentToChatPayload.payload = payload; + // @ts-expect-error-ignore + const x = messageSentToChatPayload?.subscribe(_parent, _args, context); + expect(x).not.toBe(null); + expect(await filterFunction(payload, variables)).toBe(false); + }); +}); diff --git a/tests/resolvers/Subscription/messageSentToDirectChat.spec.ts b/tests/resolvers/Subscription/messageSentToDirectChat.spec.ts deleted file mode 100644 index 8a6fdff2456..00000000000 --- a/tests/resolvers/Subscription/messageSentToDirectChat.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import "dotenv/config"; -import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import type { TestDirectChatMessageType } from "../../helpers/directChat"; -import { createTestDirectChatMessage } from "../../helpers/directChat"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import { filterFunction } from "../../../src/resolvers/Subscription/messageSentToDirectChat"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testDirectChatMessage: TestDirectChatMessageType; -let testCurrentUser: TestUserType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestDirectChatMessage(); - testCurrentUser = resultArray[0]; - testDirectChatMessage = resultArray[3]; -}); -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("src -> resolvers -> Subscription -> messageSentToDirectChat", () => { - it("subscription filter function returns true if CurrentUser is receiveror sender", async () => { - const { messageSentToDirectChat: messageSentToDirectChatPayload } = - await import( - "../../../src/resolvers/Subscription/messageSentToDirectChat" - ); - - const _args = {}; - const _parent = {}; - const context = { - pubsub: { - asyncIterator: (_action: "MESSAGE_SENT_TO_DIRECT_CHAT"): string => { - return _action; - }, - }, - context: { currentUserId: testCurrentUser?._id }, - }; - const variables = { - userId: testCurrentUser?._id, - }; - const payload = { - messageSentToDirectChat: { - receiver: testDirectChatMessage?.receiver, - sender: testDirectChatMessage?.sender, - }, - }; - // @ts-expect-error-ignore - messageSentToDirectChatPayload.payload = payload; - // @ts-expect-error-ignore - const x = messageSentToDirectChatPayload?.subscribe( - _parent, - _args, - context, - ); - expect(x).not.toBe(null); - expect(await filterFunction(payload, variables)).toBe(true); - - // If current User is sender - payload.messageSentToDirectChat.receiver = "receiver"; - expect(await filterFunction(payload, variables)).toBe(true); - }); - - it("user is not notified if it is not a part of DirectChat", async () => { - const { messageSentToDirectChat: messageSentToDirectChatPayload } = - await import( - "../../../src/resolvers/Subscription/messageSentToDirectChat" - ); - - const _args = {}; - const _parent = {}; - const context = { - pubsub: { - asyncIterator: (_action: "MESSAGE_SENT_TO_DIRECT_CHAT"): string => { - return _action; - }, - }, - context: { currentUserId: testCurrentUser?._id }, - }; - const variables = { - userId: testCurrentUser?._id, - }; - - const payload = { - messageSentToDirectChat: { - receiver: "Receiver", - sender: "Sender", - }, - }; - // @ts-expect-error-ignore - messageSentToDirectChatPayload.payload = payload; - // @ts-expect-error-ignore - const x = messageSentToDirectChatPayload?.subscribe( - _parent, - _args, - context, - ); - expect(x).not.toBe(null); - expect(await filterFunction(payload, variables)).toBe(false); - }); -}); diff --git a/tests/resolvers/Subscription/messageSentToGroupChat.spec.ts b/tests/resolvers/Subscription/messageSentToGroupChat.spec.ts deleted file mode 100644 index c1aa13072a6..00000000000 --- a/tests/resolvers/Subscription/messageSentToGroupChat.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import "dotenv/config"; -import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { connect, disconnect } from "../../helpers/db"; -import mongoose from "mongoose"; -import type { TestGroupChatType } from "../../helpers/groupChat"; -import { createTestGroupChatMessage } from "../../helpers/groupChat"; -import { filterFunction } from "../../../src/resolvers/Subscription/messageSentToGroupChat"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testGroupChat: TestGroupChatType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - testGroupChat = (await createTestGroupChatMessage())[2]; -}); -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("src -> resolvers -> Subscription -> messageSentToGroupChat", () => { - it("subscription filter function returns true", async () => { - const { messageSentToGroupChat: messageSentToGroupChatPayload } = - await import( - "../../../src/resolvers/Subscription/messageSentToGroupChat" - ); - - const _args = {}; - const _parent = {}; - const context = { - pubsub: { - asyncIterator: (_action: "MESSAGE_SENT_TO_GROUP_CHAT"): string => { - return _action; - }, - }, - context: { currentUserId: testGroupChat?.users[0] }, - }; - const variables = { - userId: testGroupChat?.users[0], - }; - const payload = { - messageSentToGroupChat: { - groupChatMessageBelongsTo: testGroupChat?._id, - }, - }; - // @ts-expect-error-ignore - messageSentToGroupChatPayload.payload = payload; - // @ts-expect-error-ignore - const x = messageSentToGroupChatPayload?.subscribe(_parent, _args, context); - expect(x).not.toBe(null); - expect(await filterFunction(payload, variables)).toBe(true); - }); - it("subscription filter function returns false when group chat not found with the id", async () => { - const { messageSentToGroupChat: messageSentToGroupChatPayload } = - await import( - "../../../src/resolvers/Subscription/messageSentToGroupChat" - ); - - const _args = {}; - const _parent = {}; - const context = { - pubsub: { - asyncIterator: (_action: "MESSAGE_SENT_TO_GROUP_CHAT"): string => { - return _action; - }, - }, - context: { currentUserId: testGroupChat?.users[0] }, - }; - const payload = { - messageSentToGroupChat: { - groupChatMessageBelongsTo: new mongoose.Types.ObjectId(), - }, - }; - const variables = { - userId: testGroupChat?.users[0], - }; - // @ts-expect-error-ignore - messageSentToGroupChatPayload.payload = payload; - // @ts-expect-error-ignore - const x = messageSentToGroupChatPayload?.subscribe(_parent, _args, context); - expect(x).not.toBe(null); - expect(await filterFunction(payload, variables)).toBe(false); - }); -}); diff --git a/tests/resolvers/User/tagsAssignedWith.spec.ts b/tests/resolvers/User/tagsAssignedWith.spec.ts new file mode 100644 index 00000000000..6c8fd975dc0 --- /dev/null +++ b/tests/resolvers/User/tagsAssignedWith.spec.ts @@ -0,0 +1,128 @@ +import "dotenv/config"; +import { + parseCursor, + tagsAssignedWith as tagsAssignedWithResolver, +} from "../../../src/resolvers/User/tagsAssignedWith"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { TestUserTagType } from "../../helpers/tags"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; +import { createTagsAndAssignToUser } from "../../helpers/tags"; +import { GraphQLError } from "graphql"; +import type { DefaultGraphQLArgumentError } from "../../../src/utilities/graphQLConnection"; +import { + type InterfaceUser, + TagUser, + OrganizationTagUser, +} from "../../../src/models"; +import { Types } from "mongoose"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testTag: TestUserTagType, + testUser: TestUserType, + testOrganization: TestOrganizationType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [testUser, testOrganization, [testTag]] = await createTagsAndAssignToUser(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("tagsAssignedWith resolver", () => { + it(`throws GraphQLError if invalid arguments are provided to the resolver`, async () => { + const parent = testUser as InterfaceUser; + try { + await tagsAssignedWithResolver?.(parent, {}, {}); + } catch (error) { + if (error instanceof GraphQLError) { + expect(error.extensions.code).toEqual("INVALID_ARGUMENTS"); + expect( + (error.extensions.errors as DefaultGraphQLArgumentError[]).length, + ).toBeGreaterThan(0); + } + } + }); + + it(`returns the expected connection object`, async () => { + const parent = testUser as InterfaceUser; + const connection = await tagsAssignedWithResolver?.( + parent, + { + first: 3, + organizationId: testOrganization?._id, + }, + {}, + ); + + const tagUser = await TagUser.findOne({ + tagId: testTag?._id, + userId: testUser?._id, + }); + + const tag = await OrganizationTagUser.findOne({ + _id: testTag?._id, + }); + + const totalCount = await TagUser.find({ + userId: testUser?._id, + }).countDocuments(); + + expect(connection).toEqual({ + edges: [ + { + cursor: tagUser?._id.toString(), + node: tag?.toObject(), + }, + ], + pageInfo: { + endCursor: tagUser?._id.toString(), + hasNextPage: false, + hasPreviousPage: false, + startCursor: tagUser?._id.toString(), + }, + totalCount, + }); + }); +}); + +describe("parseCursor function", () => { + it("returns failure state if argument cursorValue is an invalid cursor", async () => { + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: new Types.ObjectId().toString(), + userId: testUser?._id.toString() as string, + }); + + expect(result.isSuccessful).toEqual(false); + if (result.isSuccessful === false) { + expect(result.errors.length).toBeGreaterThan(0); + } + }); + + it("returns success state if argument cursorValue is a valid cursor", async () => { + const tagUser = await TagUser.findOne({ + tagId: testTag?._id, + userId: testUser?._id, + }); + + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: tagUser?._id.toString() as string, + userId: testUser?._id.toString() as string, + }); + + expect(result.isSuccessful).toEqual(true); + if (result.isSuccessful === true) { + expect(result.parsedCursor).toEqual(tagUser?._id.toString()); + } + }); +}); diff --git a/tests/resolvers/User/user.spec.ts b/tests/resolvers/User/user.spec.ts new file mode 100644 index 00000000000..02497663e61 --- /dev/null +++ b/tests/resolvers/User/user.spec.ts @@ -0,0 +1,56 @@ +import "dotenv/config"; +import { connect, disconnect } from "../../helpers/db"; +import { beforeEach, describe, it, expect, beforeAll, afterAll } from "vitest"; +import { User } from "../../../src/models"; +import type mongoose from "mongoose"; +import { createTestUserFunc } from "../../helpers/user"; +import type { TestUserType } from "../../helpers/user"; +let MONGOOSE_INSTANCE: typeof mongoose; +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("User Identifier Tests", () => { + beforeEach(async () => { + // Clear the User collection bef ore each test + await User.deleteMany({}); + }); + it("identifier should be a numeric value", async () => { + const user1: TestUserType = await createTestUserFunc(); + if (user1) { + await user1.save(); + expect(typeof user1.identifier === "number").toBe(true); + } + }); + it("should be immutable", async () => { + const user1: TestUserType = await createTestUserFunc(); + if (user1) { + try { + await User.findOneAndUpdate( + { _id: user1._id }, + { $set: { identifier: 1 } }, + { new: true }, + ); + } catch (error) { + expect((error as Error).name).to.equal("ValidationError"); + } + } + }); + it("identifier should be sequential and incremental", async () => { + const user1: TestUserType = await createTestUserFunc(); + const user2: TestUserType = await createTestUserFunc(); + + if (user1 && user2) { + await user1.save(); + expect(typeof user1.identifier === "number").toBe(true); + + await user2.save(); + expect(typeof user2.identifier === "number").toBe(true); + expect(user2.identifier).toBeGreaterThan(user1.identifier); + } + }); +}); diff --git a/tests/resolvers/UserTag/ancestorTags.spec.ts b/tests/resolvers/UserTag/ancestorTags.spec.ts new file mode 100644 index 00000000000..dd06ea52796 --- /dev/null +++ b/tests/resolvers/UserTag/ancestorTags.spec.ts @@ -0,0 +1,50 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import type { InterfaceOrganizationTagUser } from "../../../src/models"; +import { OrganizationTagUser } from "../../../src/models"; +import { ancestorTags as ancestorTagsResolver } from "../../../src/resolvers/UserTag/ancestorTags"; +import { connect, disconnect } from "../../helpers/db"; +import type { TestUserTagType } from "../../helpers/tags"; +import { createTwoLevelTagsWithOrg } from "../../helpers/tags"; +import type { TestOrganizationType } from "../../helpers/userAndOrg"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testRootTag: TestUserTagType, + testSubTagLevel1: TestUserTagType, + testSubTagLevel2: TestUserTagType; +let testOrganization: TestOrganizationType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [, testOrganization, [testRootTag, testSubTagLevel1]] = + await createTwoLevelTagsWithOrg(); + + testSubTagLevel2 = await OrganizationTagUser.create({ + name: "testSubTagLevel2", + parentTagId: testSubTagLevel1?._id, + organizationId: testOrganization?._id, + }); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Tag -> ancestorTags", () => { + it(`returns an empty ancestorTags array for the root tag`, async () => { + const parent = testRootTag as InterfaceOrganizationTagUser; + + const payload = await ancestorTagsResolver?.(parent, {}, {}); + + expect(payload).toEqual([]); + }); + + it(`returns the correct ancestorTags array for a nested tag`, async () => { + const parent = testSubTagLevel2 as InterfaceOrganizationTagUser; + + const payload = await ancestorTagsResolver?.(parent, {}, {}); + + expect(payload).toEqual([testRootTag, testSubTagLevel1]); + }); +}); diff --git a/tests/resolvers/UserTag/childTags.spec.ts b/tests/resolvers/UserTag/childTags.spec.ts index 463dbb3e51e..bb3aab877d3 100644 --- a/tests/resolvers/UserTag/childTags.spec.ts +++ b/tests/resolvers/UserTag/childTags.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import "dotenv/config"; import { GraphQLError } from "graphql"; import type mongoose from "mongoose"; diff --git a/tests/resolvers/UserTag/usersAssignedTo.spec.ts b/tests/resolvers/UserTag/usersAssignedTo.spec.ts index 52c52469eec..03339543530 100644 --- a/tests/resolvers/UserTag/usersAssignedTo.spec.ts +++ b/tests/resolvers/UserTag/usersAssignedTo.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import "dotenv/config"; import { parseCursor, @@ -8,7 +7,11 @@ import { connect, disconnect } from "../../helpers/db"; import type mongoose from "mongoose"; import { beforeAll, afterAll, describe, it, expect } from "vitest"; import type { TestUserTagType } from "../../helpers/tags"; -import type { TestUserType } from "../../helpers/userAndOrg"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; +import { createTestUser } from "../../helpers/userAndOrg"; import { createTagsAndAssignToUser } from "../../helpers/tags"; import { GraphQLError } from "graphql"; import type { DefaultGraphQLArgumentError } from "../../../src/utilities/graphQLConnection"; @@ -20,11 +23,30 @@ import { import { Types } from "mongoose"; let MONGOOSE_INSTANCE: typeof mongoose; -let testTag: TestUserTagType, testUser: TestUserType; +let testTag: TestUserTagType, + testUser: TestUserType, + testOrganization: TestOrganizationType, + randomUser: TestUserType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - [testUser, , [testTag]] = await createTagsAndAssignToUser(); + [testUser, testOrganization, [testTag]] = await createTagsAndAssignToUser(); + randomUser = await createTestUser(); + + await User.updateOne( + { + _id: randomUser?._id, + }, + { + joinedOrganizations: testOrganization?._id, + }, + ); + + await TagUser.create({ + tagId: testTag?._id, + userId: randomUser?._id, + organizationId: testTag?.organizationId, + }); }); afterAll(async () => { @@ -56,15 +78,26 @@ describe("usersAssignedTo resolver", () => { {}, ); - const tagUser = await TagUser.findOne({ + const tagUser1 = await TagUser.findOne({ tagId: testTag?._id, userId: testUser?._id, + organizationId: testTag?.organizationId, }); - const user = await User.findOne({ + const tagUser2 = await TagUser.findOne({ + tagId: testTag?._id, + userId: randomUser?._id, + organizationId: testTag?.organizationId, + }); + + const user1 = await User.findOne({ _id: testUser?._id, }); + const user2 = await User.findOne({ + _id: randomUser?._id, + }); + const totalCount = await TagUser.find({ tagId: testTag?._id, }).countDocuments(); @@ -72,15 +105,66 @@ describe("usersAssignedTo resolver", () => { expect(connection).toEqual({ edges: [ { - cursor: tagUser?._id.toString(), - node: user?.toObject(), + cursor: tagUser2?._id.toString(), + node: user2?.toObject(), + }, + { + cursor: tagUser1?._id.toString(), + node: user1?.toObject(), }, ], pageInfo: { - endCursor: tagUser?._id.toString(), + endCursor: tagUser1?._id.toString(), hasNextPage: false, hasPreviousPage: false, - startCursor: tagUser?._id.toString(), + startCursor: tagUser2?._id.toString(), + }, + totalCount, + }); + }); + + it(`returns the expected connection object, after a specified cursor`, async () => { + const tagUser1 = await TagUser.findOne({ + tagId: testTag?._id, + userId: testUser?._id, + }).lean(); + + const parent = testTag as InterfaceOrganizationTagUser; + const connection = await usersAssignedToResolver?.( + parent, + { + first: 1, + after: tagUser1?._id.toString(), + sortedBy: { id: "ASCENDING" }, + }, + {}, + ); + + const tagUser2 = await TagUser.findOne({ + tagId: testTag?._id, + userId: randomUser?._id, + }); + + const user2 = await User.findOne({ + _id: randomUser?._id, + }); + + const totalCount = await TagUser.find({ + tagId: testTag?._id, + }).countDocuments(); + + expect(connection).toEqual({ + edges: [ + { + cursor: tagUser2?._id.toString(), + node: user2?.toObject(), + }, + ], + pageInfo: { + endCursor: tagUser2?._id.toString(), + hasNextPage: false, + hasPreviousPage: true, + startCursor: tagUser2?._id.toString(), }, totalCount, }); diff --git a/tests/resolvers/UserTag/usersToAssignTo.spec.ts b/tests/resolvers/UserTag/usersToAssignTo.spec.ts new file mode 100644 index 00000000000..3b502b7db0a --- /dev/null +++ b/tests/resolvers/UserTag/usersToAssignTo.spec.ts @@ -0,0 +1,116 @@ +import "dotenv/config"; +import { + parseCursor, + usersToAssignTo as usersToAssignToResolver, +} from "../../../src/resolvers/UserTag/usersToAssignTo"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { TestUserTagType } from "../../helpers/tags"; +import type { TestUserType } from "../../helpers/userAndOrg"; +import { createRootTagWithOrg } from "../../helpers/tags"; +import { GraphQLError } from "graphql"; +import type { DefaultGraphQLArgumentError } from "../../../src/utilities/graphQLConnection"; +import { User, type InterfaceOrganizationTagUser } from "../../../src/models"; +import { Types } from "mongoose"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testTag: TestUserTagType, testUser: TestUserType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [testUser, , testTag] = await createRootTagWithOrg(); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("usersToAssignTo resolver", () => { + it(`throws GraphQLError if invalid arguments are provided to the resolver`, async () => { + const parent = testTag as InterfaceOrganizationTagUser; + + try { + await usersToAssignToResolver?.(parent, {}, {}); + } catch (error) { + if (error instanceof GraphQLError) { + expect(error.extensions.code).toEqual("INVALID_ARGUMENTS"); + expect( + (error.extensions.errors as DefaultGraphQLArgumentError[]).length, + ).toBeGreaterThan(0); + } + } + }); + + it(`returns the expected connection object`, async () => { + const parent = testTag as InterfaceOrganizationTagUser; + + const connection = await usersToAssignToResolver?.( + parent, + { + first: 3, + }, + {}, + ); + + const user = await User.findOne({ + _id: testUser?._id, + }).lean(); + + const totalCount = 1; // only one user belonging to the organization + + expect(connection).toEqual({ + edges: [ + { + cursor: testUser?._id.toString(), + node: { + ...user, + _id: user?._id.toString(), + tagUsers: [], + }, + }, + ], + pageInfo: { + endCursor: testUser?._id.toString(), + hasNextPage: false, + hasPreviousPage: false, + startCursor: testUser?._id.toString(), + }, + totalCount, + }); + }); +}); + +describe("parseCursor function", () => { + it("returns failure state if argument cursorValue is an invalid cursor", async () => { + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: new Types.ObjectId().toString(), + }); + + expect(result.isSuccessful).toEqual(false); + + if (result.isSuccessful === false) { + expect(result.errors.length).toBeGreaterThan(0); + } + }); + + it("returns success state if argument cursorValue is a valid cursor", async () => { + const parserCursorValue = await User.findOne({ + _id: testUser?._id, + }); + + const result = await parseCursor({ + cursorName: "after", + cursorPath: ["after"], + cursorValue: parserCursorValue?._id.toString() as string, + }); + + expect(result.isSuccessful).toEqual(true); + + if (result.isSuccessful === true) { + expect(result.parsedCursor).toEqual(parserCursorValue?._id.toString()); + } + }); +}); diff --git a/tests/resolvers/middleware/currentUserExists.spec.ts b/tests/resolvers/middleware/currentUserExists.spec.ts index 3cc0a4fcedf..0dd3707e897 100644 --- a/tests/resolvers/middleware/currentUserExists.spec.ts +++ b/tests/resolvers/middleware/currentUserExists.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import "dotenv/config"; import type mongoose from "mongoose"; import { Types } from "mongoose"; diff --git a/tests/services/createFile.spec.ts b/tests/services/createFile.spec.ts new file mode 100644 index 00000000000..7e096f966dd --- /dev/null +++ b/tests/services/createFile.spec.ts @@ -0,0 +1,110 @@ +/** + * module - createFile + * + * This module contains unit tests for the `createFile` function in the file service. + * The tests utilize Vitest for mocking and asserting behavior. + * + * ### Tests + * + * - **Test Case 1**: `should create a new file document when no existing file is found` + * - Verifies that when a file with a specific hash does not exist, a new file document is created in the database. + * - It checks that `File.findOne` is called with the correct hash and that `File.create` is invoked with the expected file details. + * + * - **Test Case 2**: `should increment reference count if an existing file is found` + * - Confirms that if a file with the same hash already exists, the reference count for that file is incremented. + * - It ensures that the `save` method of the existing file is called and that `File.create` is not invoked. + * + * @example + * import \{ createFile \} from "./path/to/createFile"; + * + * const result = await createFile(uploadResult, originalname, mimetype, size); + * + * @see {@link ../../src/REST/services/file#createFile} + * @see {@link ../../src/models#File} + */ + +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { File } from "../../src/models"; +import { createFile } from "../../src/REST/services/file"; + +vi.mock("../../src/models", () => ({ + File: { + findOne: vi.fn(), + create: vi.fn(), + }, +})); + +describe("createFile", () => { + const uploadResult = { + hash: "testHash123", + objectKey: "test/file/path", + hashAlgorithm: "sha256", + exists: false, + }; + + const originalname = "image.png"; + const mimetype = "image/png"; + const size = 2048; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should create a new file document when no existing file is found", async () => { + vi.mocked(File.findOne).mockResolvedValueOnce(null); + + const createdFile = File.create({ + fileName: originalname, + mimeType: mimetype, + size: size, + hash: { + value: uploadResult.hash, + algorithm: uploadResult.hashAlgorithm, + }, + uri: `http://localhost:3000/api/file/${uploadResult.objectKey}`, + metadata: { + objectKey: uploadResult.objectKey, + bucketName: "test-bucket", + }, + }); + + const result = await createFile(uploadResult, originalname, mimetype, size); + + expect(result).toEqual(createdFile); + expect(File.findOne).toHaveBeenCalledWith({ + "hash.value": uploadResult.hash, + }); + expect(File.create).toHaveBeenCalledWith({ + fileName: originalname, + mimeType: mimetype, + size: size, + hash: { + value: uploadResult.hash, + algorithm: uploadResult.hashAlgorithm, + }, + uri: expect.any(String), + metadata: { + objectKey: uploadResult.objectKey, + bucketName: expect.any(String), + }, + }); + }); + + it("should increment reference count if an existing file is found", async () => { + const existingFile = { + referenceCount: 1, + save: vi.fn().mockResolvedValueOnce(undefined), + }; + vi.mocked(File.findOne).mockResolvedValueOnce(existingFile); + + const result = await createFile(uploadResult, originalname, mimetype, size); + + expect(result).toEqual(existingFile); + expect(File.findOne).toHaveBeenCalledWith({ + "hash.value": uploadResult.hash, + }); + expect(existingFile.referenceCount).toBe(2); + expect(existingFile.save).toHaveBeenCalled(); + expect(File.create).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/services/deleteFile.spec.ts b/tests/services/deleteFile.spec.ts new file mode 100644 index 00000000000..0acc40f0365 --- /dev/null +++ b/tests/services/deleteFile.spec.ts @@ -0,0 +1,123 @@ +import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest"; +import { File } from "../../src/models"; +import { deleteFile } from "../../src/REST/services/file"; +import * as minioServices from "../../src/REST/services/minio"; +import { BUCKET_NAME } from "../../src/config/minio"; +import { isMinioRunning } from "../helpers/minio"; + +vi.mock("../../src/models", () => ({ + File: { + findOne: vi.fn(), + deleteOne: vi.fn(), + prototype: { + save: vi.fn(), + }, + }, +})); + +vi.mock("../../src/REST/controllers/minio", () => ({ + deleteFile: vi.fn(), +})); + +describe("src -> REST -> services -> file -> deleteFile", () => { + const objectKey = "test/file/path"; + const fileId = "12345"; + let serverRunning = false; + + // Wait for server status check before running tests + beforeAll(async () => { + try { + serverRunning = await isMinioRunning(); + } catch (error) { + console.error("Error checking MinIO server status:", error); + serverRunning = false; + } + }); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should return success:false and message if file is not found", async () => { + vi.mocked(File.findOne).mockResolvedValueOnce(null); + + const result = await deleteFile(objectKey, fileId); + + expect(result).toEqual({ success: false, message: "File not found." }); + expect(File.findOne).toHaveBeenCalledWith({ + _id: fileId, + "metadata.objectKey": objectKey, + }); + }); + + it("should decrement reference count if file exists and reference count is greater than 1", async () => { + const mockFile = { + referenceCount: 2, + save: vi.fn().mockResolvedValueOnce(undefined), + }; + vi.mocked(File.findOne).mockResolvedValueOnce(mockFile); + + const result = await deleteFile(objectKey, fileId); + + expect(result).toEqual({ + success: true, + message: "File reference count decreased successfully", + }); + expect(mockFile.referenceCount).toBe(1); + expect(mockFile.save).toHaveBeenCalled(); + }); + + // Use describe block with a dynamic condition + describe("MinIO server integration tests", () => { + beforeEach(async () => { + serverRunning = await isMinioRunning(); + }); + + it("should delete the file from the database and storage if reference count is 1", async () => { + // Skip this test if server is not running + if (!serverRunning) { + console.log("Skipping MinIO test - server not running"); + return; + } + + const mockFile = { + referenceCount: 1, + _id: fileId, + id: fileId, + }; + vi.mocked(File.findOne).mockResolvedValueOnce(mockFile); + + const deleteFileFromBucketSpy = vi.spyOn(minioServices, "deleteFile"); + + const result = await deleteFile(objectKey, fileId); + + expect(result).toEqual({ + success: true, + message: "File deleted successfully", + }); + expect(File.deleteOne).toHaveBeenCalledWith({ _id: fileId }); + expect(deleteFileFromBucketSpy).toHaveBeenCalledWith( + BUCKET_NAME, + objectKey, + ); + }); + }); + + it("should handle errors and return an error message", async () => { + const mockError = new Error("Deletion failed"); + vi.mocked(File.findOne).mockRejectedValueOnce(mockError); + + const consoleErrorSpy = vi.spyOn(console, "error"); + + const result = await deleteFile(objectKey, fileId); + + expect(result).toEqual({ + success: false, + message: "Error occurred while deleting file", + }); + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Error deleting file:", + mockError, + ); + }); +}); diff --git a/tests/services/uploadFile.spec.ts b/tests/services/uploadFile.spec.ts new file mode 100644 index 00000000000..6c175fad997 --- /dev/null +++ b/tests/services/uploadFile.spec.ts @@ -0,0 +1,162 @@ +/** + * Tests for the file upload functionality. + * + * This test suite validates the uploadFile service which handles file uploads in the application. + * The service processes incoming files, validates their types, uploads them to storage, and creates + * corresponding database records. + * + * Test Coverage: + * - Input validation for missing files + * - MIME type validation for uploaded files + * - Successful file upload flow including storage and database operations + * - Error handling for upload failures + * + * Mock Strategy: + * - External services (uploadMedia, createFile) are mocked to isolate the upload logic + * - File validation utilities are mocked to control validation responses + * - Express Request/Response objects are mocked with necessary properties + * + * package - REST/services/file + */ + +import { describe, it, expect, vi, beforeEach } from "vitest"; +import type { Request, Response } from "express"; + +import { isValidMimeType } from "../../src/utilities/isValidMimeType"; +import { errors, requestContext } from "../../src/libraries"; +import { FILE_NOT_FOUND } from "../../src/constants"; +import { BUCKET_NAME } from "../../src/config/minio"; +import { createFile, uploadFile } from "../../src/REST/services/file"; +import { uploadMedia } from "../../src/REST/services/minio"; +import { Types } from "mongoose"; + +vi.mock("../../src/REST/services/minio", () => ({ + uploadMedia: vi.fn(), +})); + +vi.mock("../../src/REST/services/file/createFile", () => ({ + createFile: vi.fn(), +})); + +vi.mock("../../src/utilities/isValidMimeType", () => ({ + isValidMimeType: vi.fn(), +})); + +vi.mock("../../src/libraries/requestContext", () => ({ + translate: (message: string): string => `Translated ${message}`, +})); + +describe("uploadFile", () => { + const mockFile = { + buffer: Buffer.from("mock file content"), + mimetype: "image/png", + originalname: "test-image.png", + size: 1024, + }; + + const mockRequest = { + file: mockFile, + } as unknown as Request; + + const mockResponse = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + } as unknown as Response; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should return error if no file is uploaded", async () => { + const reqWithoutFile = {} as Request; + + await expect(uploadFile(reqWithoutFile, mockResponse)).rejects.toThrow( + errors.InputValidationError, + ); + + expect(mockResponse.status).toHaveBeenCalledWith(400); + expect(mockResponse.json).toHaveBeenCalledWith({ + error: requestContext.translate(FILE_NOT_FOUND.MESSAGE), + }); + }); + + it("should return error if file type is invalid", async () => { + vi.mocked(isValidMimeType).mockReturnValueOnce(false); + + await expect(uploadFile(mockRequest, mockResponse)).rejects.toThrow( + errors.InputValidationError, + ); + + expect(isValidMimeType).toHaveBeenCalledWith(mockFile.mimetype); + }); + + it("should upload file and return file document data", async () => { + const mockUploadResult = { + hash: "mockhash", + objectKey: "mock-object-key", + hashAlgorithm: "SHA-256", + exists: false, + }; + + const mockFileDoc = { + _id: new Types.ObjectId(), + fileName: "test-image.png", + mimeType: "image/png", + size: 1024, + hash: { + value: "mockhash", + algorithm: "SHA-256", + }, + uri: "http://localhost/api/file/mock-object-key", + referenceCount: 1, + metadata: { + objectKey: "mock-object-key", + }, + encryption: false, + archived: false, + visibility: "PUBLIC" as const, + backupStatus: "", + status: "ACTIVE" as const, + createdAt: new Date(), + updatedAt: new Date(), + }; + + vi.mocked(isValidMimeType).mockReturnValueOnce(true); + vi.mocked(uploadMedia).mockResolvedValueOnce(mockUploadResult); + vi.mocked(createFile).mockResolvedValueOnce(mockFileDoc); + + const result = await uploadFile(mockRequest, mockResponse); + + expect(uploadMedia).toHaveBeenCalledWith( + BUCKET_NAME, + mockFile.buffer, + mockFile.originalname, + { ContentType: mockFile.mimetype }, + ); + expect(createFile).toHaveBeenCalledWith( + mockUploadResult, + mockFile.originalname, + mockFile.mimetype, + mockFile.size, + ); + expect(result).toEqual({ + uri: mockFileDoc.uri, + _id: mockFileDoc._id, + visibility: mockFileDoc.visibility, + objectKey: mockFileDoc.metadata.objectKey, + }); + }); + + it("should return an internal server error if upload fails", async () => { + const mockError = new Error("Failed to upload"); + + vi.mocked(isValidMimeType).mockReturnValueOnce(true); + vi.mocked(uploadMedia).mockRejectedValueOnce(mockError); + + await expect(uploadFile(mockRequest, mockResponse)).rejects.toThrow( + errors.InternalServerError, + ); + + expect(mockResponse.status).not.toHaveBeenCalledWith(400); + }); +}); diff --git a/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.default.spec.ts b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.default.spec.ts new file mode 100644 index 00000000000..1781f90863a --- /dev/null +++ b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.default.spec.ts @@ -0,0 +1,14 @@ +import { describe, it, expect, vi } from "vitest"; +import { getMinioBinaryUrl } from "../../../../src/setup/getMinioBinaryUrl"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("unsupported-platform"), +})); + +describe("getMinioBinaryUrl - Unsupported Platform", () => { + it("should throw an error for unsupported platform", () => { + expect(() => { + getMinioBinaryUrl(); + }).toThrowError(new Error("Unsupported platform: unsupported-platform")); + }); +}); diff --git a/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.linux.spec.ts b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.linux.spec.ts new file mode 100644 index 00000000000..8f4fdc12f77 --- /dev/null +++ b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.linux.spec.ts @@ -0,0 +1,15 @@ +import { describe, it, expect, vi } from "vitest"; +import { getMinioBinaryUrl } from "../../../../src/setup/getMinioBinaryUrl"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("linux"), +})); + +describe("getMinioBinaryUrl - Linux", () => { + it("should return the correct URL for Linux", () => { + const result = getMinioBinaryUrl(); + expect(result).toBe( + "https://dl.min.io/server/minio/release/linux-amd64/minio", + ); + }); +}); diff --git a/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.macos.spec.ts b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.macos.spec.ts new file mode 100644 index 00000000000..ad16f5b5da9 --- /dev/null +++ b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.macos.spec.ts @@ -0,0 +1,15 @@ +import { describe, it, expect, vi } from "vitest"; +import { getMinioBinaryUrl } from "../../../../src/setup/getMinioBinaryUrl"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("darwin"), +})); + +describe("getMinioBinaryUrl - macOS", () => { + it("should return the correct URL for macOS", () => { + const result = getMinioBinaryUrl(); + expect(result).toBe( + "https://dl.min.io/server/minio/release/darwin-amd64/minio", + ); + }); +}); diff --git a/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.windows.spec.ts b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.windows.spec.ts new file mode 100644 index 00000000000..ba49a3b8fee --- /dev/null +++ b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.windows.spec.ts @@ -0,0 +1,15 @@ +import { describe, it, expect, vi } from "vitest"; +import { getMinioBinaryUrl } from "../../../../src/setup/getMinioBinaryUrl"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("win32"), +})); + +describe("getMinioBinaryUrl - Windows", () => { + it("should return the correct URL for Windows", () => { + const result = getMinioBinaryUrl(); + expect(result).toBe( + "https://dl.min.io/server/minio/release/windows-amd64/minio.exe", + ); + }); +}); diff --git a/tests/setup/minio/installMinio/installMinio.spec.ts b/tests/setup/minio/installMinio/installMinio.spec.ts new file mode 100644 index 00000000000..f189d85c497 --- /dev/null +++ b/tests/setup/minio/installMinio/installMinio.spec.ts @@ -0,0 +1,270 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import * as os from "os"; +import * as fs from "fs"; +import * as path from "path"; +import * as https from "https"; +import { getMinioBinaryUrl } from "../../../../src/setup/getMinioBinaryUrl"; +import { installMinio } from "../../../../src/setup/installMinio"; +import { setPathEnvVar } from "../../../../src/setup/setPathEnvVar"; +import type { ClientRequest, IncomingMessage } from "http"; + +const mockMinioUrl = "https://dl.min.io/server/minio/release/linux-amd64/minio"; + +vi.mock("https", () => ({ + get: vi.fn( + ( + url: string | URL | https.RequestOptions, + options?: https.RequestOptions | ((res: IncomingMessage) => void), + callback?: (res: IncomingMessage) => void, + ): ClientRequest => { + const mockResponse = { + pipe: vi.fn(), + } as unknown as IncomingMessage; + + if (typeof options === "function") { + options(mockResponse); + } else if (callback) { + callback(mockResponse); + } + + return { + on: vi.fn().mockReturnThis(), + } as unknown as ClientRequest; + }, + ), +})); + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("linux"), + homedir: vi.fn(), +})); +const mockWriteStream = { + close: vi.fn().mockImplementation((callback) => callback()), + on: vi.fn().mockImplementation((event, handler) => { + if (event === "finish") { + // Immediately call the 'finish' handler + handler(); + } + return mockWriteStream; + }), +}; + +vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, +); + +vi.mock("fs"); +vi.mock("path"); +vi.mock("dotenv"); +vi.mock("../../../../src/setup/setPathEnvVar", () => ({ + setPathEnvVar: vi.fn(), +})); +vi.mock("../../../../src/setup/getMinioBinaryUrl", () => ({ + getMinioBinaryUrl: vi.fn(), +})); + +describe("installMinio", () => { + const mockHomedir = "/mock/home"; + const mockInstallDir = "/mock/home/.minio"; + const mockMinioPath = "/mock/home/.minio/minio"; + + beforeEach(() => { + vi.mocked(os.homedir).mockReturnValue(mockHomedir); + vi.mocked(path.join).mockImplementation((...args) => args.join("/")); + vi.mocked(getMinioBinaryUrl).mockReturnValue(mockMinioUrl); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("should create installation directory if it does not exist", async () => { + vi.mocked(fs.existsSync).mockReturnValue(false); + const mkdirSyncMock = vi.mocked(fs.mkdirSync); + + const mockWriteStream = { + close: vi.fn().mockImplementation((callback: () => void) => callback()), + on: vi.fn().mockImplementation((event: string, handler: () => void) => { + if (event === "finish") { + handler(); + } + return mockWriteStream; + }), + }; + + vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, + ); + vi.mocked(fs.chmodSync).mockImplementation(() => {}); + + await installMinio(); + + expect(mkdirSyncMock).toHaveBeenCalledWith(mockInstallDir, { + recursive: true, + }); + expect(vi.mocked(https.get)).toHaveBeenCalledWith( + mockMinioUrl, + expect.any(Function), + ); + expect(fs.createWriteStream).toHaveBeenCalledWith(mockMinioPath); + expect(mockWriteStream.on).toHaveBeenCalledWith( + "finish", + expect.any(Function), + ); + expect(mockWriteStream.close).toHaveBeenCalled(); + expect(fs.chmodSync).toHaveBeenCalledWith(mockMinioPath, 0o755); + expect(vi.mocked(setPathEnvVar)).toHaveBeenCalledWith(mockInstallDir); + }); + + it("should download and save the Minio binary", async () => { + const mockWriteStream = { + close: vi.fn().mockImplementation((callback) => callback()), + on: vi.fn().mockImplementation((event, handler) => { + if (event === "finish") { + handler(); + } + return mockWriteStream; + }), + }; + + vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, + ); + + vi.mocked(https.get).mockImplementation((_url, options, callback) => { + const cb = typeof options === "function" ? options : callback; + const mockResponse = { + pipe: vi.fn().mockReturnValue(mockWriteStream), + }; + if (typeof cb === "function") { + cb(mockResponse as unknown as IncomingMessage); + } + return { + on: vi.fn().mockReturnThis(), + } as never; + }); + + await installMinio(); + + expect(fs.createWriteStream).toHaveBeenCalledWith(mockMinioPath); + expect(mockWriteStream.on).toHaveBeenCalledWith( + "finish", + expect.any(Function), + ); + }); + + it("should set permissions and update PATH", async () => { + const mockWriteStream = { + close: vi.fn().mockImplementation((callback) => callback()), + on: vi.fn().mockImplementation((event, handler) => { + if (event === "finish") { + handler(); + } + return mockWriteStream; + }), + }; + + vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, + ); + vi.mocked(https.get).mockImplementation((_url, options, callback) => { + const cb = typeof options === "function" ? options : callback; + const mockResponse = { + pipe: vi.fn().mockReturnValue(mockWriteStream), + }; + if (typeof cb === "function") { + cb(mockResponse as unknown as IncomingMessage); + } + return { + on: vi.fn().mockReturnThis(), + } as never; + }); + + await installMinio(); + + expect(fs.chmodSync).toHaveBeenCalledWith(mockMinioPath, 0o755); + expect(setPathEnvVar).toHaveBeenCalledWith(mockInstallDir); + }); + + it("should handle errors during directory creation", async () => { + vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(fs.mkdirSync).mockImplementation(() => { + throw new Error("Directory creation failed"); + }); + + await expect(installMinio()).rejects.toThrow("Failed to create directory"); + }); + + it("should handle errors during download", async () => { + vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(fs.createWriteStream).mockReturnValue({ + close: vi.fn(), + on: vi.fn(), + } as unknown as fs.WriteStream); + + vi.mocked(https.get).mockImplementation((_url, options, callback) => { + const cb = typeof options === "function" ? options : callback; + if (typeof cb === "function") { + cb({ + pipe: vi.fn(), + } as unknown as IncomingMessage); + } + return { + on: vi.fn().mockImplementation((event, handler) => { + if (event === "error") { + handler(new Error("Download failed")); + } + return this; + }), + } as never; + }); + + vi.mocked(fs.unlinkSync).mockImplementation(() => {}); + + await expect(installMinio()).rejects.toThrow( + "Failed to download Minio binary", + ); + expect(fs.unlinkSync).toHaveBeenCalledWith(mockMinioPath); + }); + + it("should handle errors during setting permissions or updating PATH", async () => { + const mockWriteStream = { + close: vi.fn().mockImplementation((callback) => callback()), + on: vi.fn().mockImplementation((event, handler) => { + if (event === "finish") { + handler(); + } + return mockWriteStream; + }), + }; + + vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, + ); + + vi.mocked(https.get).mockImplementation((_url, options, callback) => { + const cb = typeof options === "function" ? options : callback; + const mockResponse = { + pipe: vi.fn().mockReturnValue(mockWriteStream), + }; + if (typeof cb === "function") { + cb(mockResponse as unknown as IncomingMessage); + } + return { + on: vi.fn().mockReturnThis(), + } as never; + }); + + vi.mocked(fs.chmodSync).mockImplementation(() => { + throw new Error("Permission denied"); + }); + + await expect(installMinio()).rejects.toThrow( + "Failed to set permissions or PATH environment variable: Permission denied", + ); + + expect(fs.chmodSync).toHaveBeenCalledWith(mockMinioPath, 0o755); + expect(setPathEnvVar).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/setup/minio/installMinio/installMinio.windows.spec.ts b/tests/setup/minio/installMinio/installMinio.windows.spec.ts new file mode 100644 index 00000000000..a94158c2159 --- /dev/null +++ b/tests/setup/minio/installMinio/installMinio.windows.spec.ts @@ -0,0 +1,117 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import * as fs from "fs"; +import * as path from "path"; +import * as https from "https"; +import { getMinioBinaryUrl } from "../../../../src/setup/getMinioBinaryUrl"; +import { installMinio } from "../../../../src/setup/installMinio"; +import type { ClientRequest, IncomingMessage } from "http"; + +const mockMinioUrl = + "https://dl.min.io/server/minio/release/windows-amd64/minio.exe"; + +vi.mock("https", () => ({ + get: vi.fn( + ( + url: string | URL | https.RequestOptions, + options?: https.RequestOptions | ((res: IncomingMessage) => void), + callback?: (res: IncomingMessage) => void, + ): ClientRequest => { + const mockResponse = { + pipe: vi.fn(), + } as unknown as IncomingMessage; + + if (typeof options === "function") { + options(mockResponse); + } else if (callback) { + callback(mockResponse); + } + + return { + on: vi.fn().mockReturnThis(), + } as unknown as ClientRequest; + }, + ), +})); + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("linux"), + homedir: vi.fn(), +})); +const mockWriteStream = { + close: vi.fn().mockImplementation((callback) => callback()), + on: vi.fn().mockImplementation((event, handler) => { + if (event === "finish") { + // Immediately call the 'finish' handler + handler(); + } + return mockWriteStream; + }), +}; + +vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, +); + +vi.mock("fs"); +vi.mock("path"); +vi.mock("dotenv"); +vi.mock("../../../../src/setup/setPathEnvVar", () => ({ + setPathEnvVar: vi.fn(), +})); +vi.mock("../../../../src/setup/getMinioBinaryUrl", () => ({ + getMinioBinaryUrl: vi.fn(), +})); +vi.mock("os", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + platform: vi.fn().mockReturnValue("win32"), + homedir: vi.fn().mockReturnValue("/mock/home"), + }; +}); + +describe("installMinio", () => { + const mockMinioPath = "/mock/home/.minio/minio.exe"; + + beforeEach(() => { + vi.mocked(path.join).mockImplementation((...args) => args.join("/")); + vi.mocked(getMinioBinaryUrl).mockReturnValue(mockMinioUrl); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("should download and save the Minio binary", async () => { + const mockWriteStream = { + close: vi.fn().mockImplementation((callback) => callback()), + on: vi.fn().mockImplementation((event, handler) => { + if (event === "finish") { + handler(); + } + return mockWriteStream; + }), + }; + + vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, + ); + + vi.mocked(https.get).mockImplementation((_url, options, callback) => { + const cb = typeof options === "function" ? options : callback; + const mockResponse = { + pipe: vi.fn().mockReturnValue(mockWriteStream), + }; + if (typeof cb === "function") { + cb(mockResponse as unknown as IncomingMessage); + } + return { + on: vi.fn().mockReturnThis(), + } as never; + }); + + await installMinio(); + + expect(fs.createWriteStream).toHaveBeenCalledWith(mockMinioPath); + }); +}); diff --git a/tests/setup/minio/installMinio/installMinioSkipCreateDir.spec.ts b/tests/setup/minio/installMinio/installMinioSkipCreateDir.spec.ts new file mode 100644 index 00000000000..02d6bd4911a --- /dev/null +++ b/tests/setup/minio/installMinio/installMinioSkipCreateDir.spec.ts @@ -0,0 +1,90 @@ +// installMinioSkipCreateDir.test.ts +import { describe, it, expect, vi, beforeEach } from "vitest"; +import * as os from "os"; +import * as fs from "fs"; +import * as path from "path"; +import * as https from "https"; +import type { IncomingMessage } from "http"; +import { getMinioBinaryUrl } from "../../../../src/setup/getMinioBinaryUrl"; +import { installMinio } from "../../../../src/setup/installMinio"; +import { setPathEnvVar } from "../../../../src/setup/setPathEnvVar"; + +const mockMinioUrl = "https://dl.min.io/server/minio/release/linux-amd64/minio"; + +vi.mock("https", () => ({ + get: vi.fn( + ( + url: string | URL | https.RequestOptions, + options?: https.RequestOptions | ((res: IncomingMessage) => void), + callback?: (res: IncomingMessage) => void, + ) => { + const mockResponse = { pipe: vi.fn() } as unknown as IncomingMessage; + if (typeof options === "function") { + options(mockResponse); + } else if (callback) { + callback(mockResponse); + } + return { on: vi.fn().mockReturnThis() }; + }, + ), +})); + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("linux"), + homedir: vi.fn(), +})); + +const mockWriteStream = { + close: vi.fn().mockImplementation((callback) => callback()), + on: vi.fn().mockImplementation((event, handler) => { + if (event === "finish") { + handler(); + } + return mockWriteStream; + }), +}; + +vi.mocked(fs.createWriteStream).mockReturnValue( + mockWriteStream as unknown as fs.WriteStream, +); + +vi.mock("fs"); +vi.mock("path"); +vi.mock("../../../../src/setup/setPathEnvVar", () => ({ + setPathEnvVar: vi.fn(), +})); +vi.mock("../../../../src/setup/getMinioBinaryUrl", () => ({ + getMinioBinaryUrl: vi.fn(), +})); + +describe("installMinio - Skip Create Directory", () => { + const mockHomedir = "/mock/home"; + + beforeEach(() => { + vi.mocked(os.homedir).mockReturnValue(mockHomedir); + vi.mocked(path.join).mockImplementation((...args) => args.join("/")); + vi.mocked(getMinioBinaryUrl).mockReturnValue(mockMinioUrl); + }); + + it("should not create installation directory if it already exists", async () => { + vi.mocked(fs.existsSync).mockReturnValue(true); + const mkdirSyncMock = vi.mocked(fs.mkdirSync); + vi.mocked(fs.chmodSync).mockImplementation(() => {}); + + await installMinio(); + + expect(mkdirSyncMock).not.toHaveBeenCalled(); + expect(https.get).toHaveBeenCalledWith( + expect.any(String), + expect.any(Function), + ); + expect(fs.createWriteStream).toHaveBeenCalledWith(expect.any(String)); + expect(mockWriteStream.on).toHaveBeenCalledWith( + "finish", + expect.any(Function), + ); + expect(mockWriteStream.close).toHaveBeenCalled(); + expect(fs.chmodSync).toHaveBeenCalledWith(expect.any(String), 0o755); + expect(setPathEnvVar).toHaveBeenCalledWith(expect.any(String)); + }); +}); diff --git a/tests/setup/minio/isMinioInstalled/isMinioInstalledBinaryFileExists.spec.ts b/tests/setup/minio/isMinioInstalled/isMinioInstalledBinaryFileExists.spec.ts new file mode 100644 index 00000000000..c35c3ffa3da --- /dev/null +++ b/tests/setup/minio/isMinioInstalled/isMinioInstalledBinaryFileExists.spec.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, vi, afterAll } from "vitest"; +import { execSync } from "child_process"; +import * as path from "path"; +import * as fs from "fs"; +import { isMinioInstalled } from "../../../../src/setup/isMinioInstalled"; + +vi.mock("fs", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + existsSync: vi.fn().mockReturnValue(true), + }; +}); +vi.mock("child_process", () => ({ + exec: vi.fn(), + execSync: vi.fn(), +})); +vi.mock("os", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + platform: vi.fn().mockReturnValue("linux"), + }; +}); +vi.mock("path", () => ({ join: vi.fn().mockReturnValue("/home/minio/minio") })); + +describe("isMinioInstalled - Binary File Exists", async () => { + afterAll(() => { + vi.resetAllMocks(); + vi.resetModules(); + }); + it("should return true if minio binary file exists", () => { + vi.mocked(execSync).mockImplementation((command: string) => { + if (command === "minio --version") { + throw new Error("Command not found"); + } + return Buffer.from(""); + }); + + expect(isMinioInstalled()).toBe(true); + expect(fs.existsSync).toHaveBeenCalledWith( + path.join("/home", ".minio", "minio"), + ); + }); +}); diff --git a/tests/setup/minio/isMinioInstalled/isMinioInstalledBinaryFileExists.windows.spec.ts b/tests/setup/minio/isMinioInstalled/isMinioInstalledBinaryFileExists.windows.spec.ts new file mode 100644 index 00000000000..9d59486cc3b --- /dev/null +++ b/tests/setup/minio/isMinioInstalled/isMinioInstalledBinaryFileExists.windows.spec.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, vi, afterAll } from "vitest"; +import { execSync } from "child_process"; +import * as path from "path"; +import * as fs from "fs"; +import { isMinioInstalled } from "../../../../src/setup/isMinioInstalled"; + +vi.mock("fs", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + existsSync: vi.fn().mockReturnValue(true), + }; +}); +vi.mock("child_process", () => ({ + exec: vi.fn(), + execSync: vi.fn(), + spawnSync: vi.fn(), +})); +vi.mock("os", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + platform: vi.fn().mockReturnValue("win32"), + }; +}); +vi.mock("path", () => ({ join: vi.fn().mockReturnValue("/home/minio/minio") })); + +describe("isMinioInstalled - Binary File Exists", async () => { + afterAll(() => { + vi.resetAllMocks(); + vi.resetModules(); + }); + it("should return true if minio binary file exists", () => { + vi.mocked(execSync).mockImplementation((command: string) => { + if (command === "minio --version") { + throw new Error("Command not found"); + } + return Buffer.from(""); + }); + + expect(isMinioInstalled()).toBe(true); + expect(fs.existsSync).toHaveBeenCalledWith( + path.join("/home", ".minio", "minio.exe"), + ); + }); +}); diff --git a/tests/setup/minio/isMinioInstalled/isMinioInstalledCommandAvailable.spec.ts b/tests/setup/minio/isMinioInstalled/isMinioInstalledCommandAvailable.spec.ts new file mode 100644 index 00000000000..88efcb8bafa --- /dev/null +++ b/tests/setup/minio/isMinioInstalled/isMinioInstalledCommandAvailable.spec.ts @@ -0,0 +1,18 @@ +import { describe, it, expect, vi } from "vitest"; +import { execSync } from "child_process"; +import { isMinioInstalled } from "../../../../src/setup/isMinioInstalled"; + +vi.mock("child_process", () => ({ + execSync: vi.fn(), +})); + +describe("isMinioInstalled - Command Available", () => { + it("should return true if minio command is available", () => { + vi.mocked(execSync).mockImplementation(() => "minio --version"); + + expect(isMinioInstalled()).toBe(true); + expect(execSync).toHaveBeenCalledWith("minio --version", { + stdio: "ignore", + }); + }); +}); diff --git a/tests/setup/minio/isMinioInstalled/minioNotInstalled.spec.ts b/tests/setup/minio/isMinioInstalled/minioNotInstalled.spec.ts new file mode 100644 index 00000000000..d1a530f1dbc --- /dev/null +++ b/tests/setup/minio/isMinioInstalled/minioNotInstalled.spec.ts @@ -0,0 +1,28 @@ +import { describe, it, expect, vi } from "vitest"; +import { execSync } from "child_process"; +import { isMinioInstalled } from "../../../../src/setup/isMinioInstalled"; + +vi.mock("fs", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + existsSync: vi.fn().mockReturnValue(false), + }; +}); +vi.mock("child_process", () => ({ + execSync: vi.fn(), +})); +vi.mock("os", () => ({ + homedir: vi.fn().mockReturnValue("/home"), + platform: vi.fn(), +})); + +describe("isMinioInstalled - Neither Available", () => { + it("should return false if minio command is not available and binary file does not exist", () => { + vi.mocked(execSync).mockImplementation(() => { + throw new Error("Command not found"); + }); + + expect(isMinioInstalled()).toBe(false); + }); +}); diff --git a/tests/setup/minio/setPathEnvVar/setPathEnvVar.error.spec.ts b/tests/setup/minio/setPathEnvVar/setPathEnvVar.error.spec.ts new file mode 100644 index 00000000000..664af462939 --- /dev/null +++ b/tests/setup/minio/setPathEnvVar/setPathEnvVar.error.spec.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { spawnSync } from "child_process"; +import { setPathEnvVar } from "../../../../src/setup/setPathEnvVar"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("win32"), +})); +vi.mock("child_process"); + +describe("setPathEnvVar error handling", () => { + const mockInstallDir = "C:\\mock\\install\\dir"; + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv, PATH: "C:\\original\\path" }; + vi.resetAllMocks(); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should throw an error if updating PATH fails", () => { + vi.mocked(spawnSync).mockImplementation(() => { + throw new Error("Mock error"); + }); + + expect(() => setPathEnvVar(mockInstallDir)).toThrow( + "Failed to set PATH environment variable: Mock error", + ); + }); +}); diff --git a/tests/setup/minio/setPathEnvVar/setPathEnvVar.linux.spec.ts b/tests/setup/minio/setPathEnvVar/setPathEnvVar.linux.spec.ts new file mode 100644 index 00000000000..e76dda2f7df --- /dev/null +++ b/tests/setup/minio/setPathEnvVar/setPathEnvVar.linux.spec.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { execSync } from "child_process"; +import { setPathEnvVar } from "../../../../src/setup/setPathEnvVar"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("linux"), +})); +vi.mock("child_process"); + +describe("setPathEnvVar on Linux", () => { + const mockInstallDir = "/mock/install/dir"; + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv, PATH: "/original/path" }; + vi.resetAllMocks(); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should update PATH for Linux", () => { + const mockExecSync = vi.mocked(execSync); + + setPathEnvVar(mockInstallDir); + + expect(process.env.PATH).toBe(`/original/path:${mockInstallDir}`); + expect(mockExecSync).toHaveBeenCalledWith( + `echo 'export PATH=$PATH:${mockInstallDir}' >> ~/.bashrc`, + ); + }); +}); diff --git a/tests/setup/minio/setPathEnvVar/setPathEnvVar.macos.spec.ts b/tests/setup/minio/setPathEnvVar/setPathEnvVar.macos.spec.ts new file mode 100644 index 00000000000..b2b72b487ae --- /dev/null +++ b/tests/setup/minio/setPathEnvVar/setPathEnvVar.macos.spec.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { execSync } from "child_process"; +import { setPathEnvVar } from "../../../../src/setup/setPathEnvVar"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("darwin"), +})); +vi.mock("child_process"); + +describe("setPathEnvVar on macOS", () => { + const mockInstallDir = "/mock/install/dir"; + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv, PATH: "/original/path" }; + vi.resetAllMocks(); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should update PATH for macOS", () => { + const mockExecSync = vi.mocked(execSync); + + setPathEnvVar(mockInstallDir); + + expect(process.env.PATH).toBe(`/original/path:${mockInstallDir}`); + expect(mockExecSync).toHaveBeenCalledWith( + `echo 'export PATH=$PATH:${mockInstallDir}' >> ~/.zshrc`, + ); + }); +}); diff --git a/tests/setup/minio/setPathEnvVar/setPathEnvVar.windows.spec.ts b/tests/setup/minio/setPathEnvVar/setPathEnvVar.windows.spec.ts new file mode 100644 index 00000000000..a11ad08f562 --- /dev/null +++ b/tests/setup/minio/setPathEnvVar/setPathEnvVar.windows.spec.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { spawnSync } from "child_process"; +import { setPathEnvVar } from "../../../../src/setup/setPathEnvVar"; + +vi.mock("os", () => ({ + platform: vi.fn().mockReturnValue("win32"), +})); +vi.mock("child_process"); + +describe("setPathEnvVar on Windows", () => { + const mockInstallDir = "C:\\mock\\install\\dir"; + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv, PATH: "C:\\original\\path" }; + vi.resetAllMocks(); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should update PATH for Windows", () => { + const mockSpawnSync = vi.mocked(spawnSync); + + setPathEnvVar(mockInstallDir); + + expect(mockSpawnSync).toHaveBeenCalledWith( + "setx", + ["PATH", `${process.env.PATH};${mockInstallDir}`], + { shell: true, stdio: "inherit" }, + ); + }); +}); diff --git a/tests/setup/minio/updateIgnoreFile/updateIgnoreFile.alreadyPresent.spec.ts b/tests/setup/minio/updateIgnoreFile/updateIgnoreFile.alreadyPresent.spec.ts new file mode 100644 index 00000000000..9dd298e7d5d --- /dev/null +++ b/tests/setup/minio/updateIgnoreFile/updateIgnoreFile.alreadyPresent.spec.ts @@ -0,0 +1,36 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import * as fs from "fs"; +import { updateIgnoreFile } from "../../../../src/setup/updateIgnoreFile"; + +vi.mock("fs", () => ({ + existsSync: vi.fn().mockReturnValue(true), + readFileSync: vi.fn(), + writeFileSync: vi.fn(), +})); + +vi.mock("path", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + relative: vi.fn().mockReturnValue("minio-data"), + }; +}); + +describe("updateIgnoreFile", () => { + const mockFilePath = ".gitignore"; + const mockDirectoryToIgnore = "./minio-data"; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should not update the ignore file if directoryToIgnore is already included", () => { + const existingContent = "# MinIO data directory\nminio-data/**\n"; + vi.mocked(fs.readFileSync).mockReturnValue(existingContent); + const writeFileSyncSpy = vi.spyOn(fs, "writeFileSync"); + + updateIgnoreFile(mockFilePath, mockDirectoryToIgnore); + + expect(writeFileSyncSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/setup/minio/updateIgnoreFile/updateIgnoreFile.spec.ts b/tests/setup/minio/updateIgnoreFile/updateIgnoreFile.spec.ts new file mode 100644 index 00000000000..dd17e94d006 --- /dev/null +++ b/tests/setup/minio/updateIgnoreFile/updateIgnoreFile.spec.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import * as fs from "fs"; +import * as path from "path"; +import { updateIgnoreFile } from "../../../../src/setup/updateIgnoreFile"; + +vi.mock("fs", () => ({ + existsSync: vi.fn().mockReturnValue(true), + readFileSync: vi.fn(), + writeFileSync: vi.fn(), +})); + +vi.mock("path", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + relative: vi.fn().mockReturnValue("minio-data"), + }; +}); + +describe("updateIgnoreFile", () => { + const mockFilePath = ".gitignore"; + const mockDirectoryToIgnore = "./minio-data"; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should update the ignore file if directoryToIgnore is not already included", () => { + vi.mocked(fs.readFileSync).mockReturnValue(""); + const writeFileSyncSpy = vi.spyOn(fs, "writeFileSync"); + + updateIgnoreFile(mockFilePath, mockDirectoryToIgnore); + + const expectedContent = "\n# MinIO data directory\nminio-data/**\n"; + expect(writeFileSyncSpy).toHaveBeenCalledWith( + mockFilePath, + expectedContent, + ); + }); + + it("should not update the ignore file if directoryToIgnore is already included", () => { + const existingContent = "# MinIO data directory\nminio-data/**\n"; + vi.mocked(fs.readFileSync).mockReturnValue(existingContent); + const writeFileSyncSpy = vi.spyOn(fs, "writeFileSync"); + + updateIgnoreFile(mockFilePath, mockDirectoryToIgnore); + + expect(writeFileSyncSpy).not.toHaveBeenCalled(); + }); + + it("should create the ignore file if it does not exist", () => { + vi.spyOn(path, "relative").mockReturnValue("minio-data"); + vi.spyOn(fs, "existsSync").mockReturnValue(false); + const writeFileSyncSpy = vi.spyOn(fs, "writeFileSync"); + + updateIgnoreFile(mockFilePath, mockDirectoryToIgnore); + + const expectedContent = "\n# MinIO data directory\nminio-data/**\n"; + expect(writeFileSyncSpy).toHaveBeenCalledWith( + mockFilePath, + expectedContent, + ); + }); + + it("should not update the ignore file if directoryToIgnore is outside the project root", () => { + vi.spyOn(path, "relative").mockReturnValue("../outside-directory"); + const writeFileSyncSpy = vi.spyOn(fs, "writeFileSync"); + + updateIgnoreFile(mockFilePath, "/outside-project/minio-data"); + + expect(writeFileSyncSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/setup/superAdmin.spec.ts b/tests/setup/superAdmin.spec.ts index 7a4cf5826d7..f3fe0c7b216 100644 --- a/tests/setup/superAdmin.spec.ts +++ b/tests/setup/superAdmin.spec.ts @@ -21,6 +21,29 @@ describe("Setup -> superAdmin", () => { vi.clearAllMocks(); }); + it("should validate the email and return an error message for invalid email", async () => { + const invalidEmail = "invalid-email"; + + vi.spyOn(inquirer, "prompt").mockImplementationOnce((questions: any) => { + // Assuming questions is an array + const questionArray = Array.isArray(questions) ? questions : [questions]; + const question = questionArray.find((q: any) => q.name === "email"); + const validate = question?.validate; + + if (typeof validate === "function") { + const validationResult = validate(invalidEmail); + return Promise.resolve({ + email: validationResult === true ? invalidEmail : validationResult, + }); + } + + return Promise.resolve({ email: invalidEmail }); + }); + + const result = await askForSuperAdminEmail.askForSuperAdminEmail(); + expect(result).toEqual("Invalid email. Please try again."); + }); + it("function askForSuperAdminEmail should return email as entered", async () => { const testEmail = "testemail@test.com"; vi.spyOn(inquirer, "prompt").mockImplementationOnce(() => diff --git a/tests/utilities/adminCheck.spec.ts b/tests/utilities/adminCheck.spec.ts index 84e2c417986..d5ba8b06623 100644 --- a/tests/utilities/adminCheck.spec.ts +++ b/tests/utilities/adminCheck.spec.ts @@ -16,6 +16,8 @@ import { AppUserProfile, Organization } from "../../src/models"; import { connect, disconnect } from "../helpers/db"; import type { TestOrganizationType, TestUserType } from "../helpers/userAndOrg"; import { createTestUserAndOrganization } from "../helpers/userAndOrg"; +import { adminCheck } from "../../src/utilities"; +import { requestContext } from "../../src/libraries"; let testUser: TestUserType; let testOrganization: TestOrganizationType; @@ -38,14 +40,11 @@ describe("utilities -> adminCheck", () => { }); it("throws error if userIsOrganizationAdmin === false and isUserSuperAdmin === false", async () => { - const { requestContext } = await import("../../src/libraries"); - const spy = vi .spyOn(requestContext, "translate") .mockImplementationOnce((message) => `Translated ${message}`); try { - const { adminCheck } = await import("../../src/utilities"); await adminCheck( testUser?._id, testOrganization ?? ({} as InterfaceOrganization), @@ -58,6 +57,16 @@ describe("utilities -> adminCheck", () => { expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ADMIN.MESSAGE); }); + it("Returns boolean if userIsOrganizationAdmin === false and isUserSuperAdmin === false and throwError is false", async () => { + expect( + await adminCheck( + testUser?._id, + testOrganization ?? ({} as InterfaceOrganization), + false, + ), + ).toEqual(false); + }); + it("throws no error if userIsOrganizationAdmin === false and isUserSuperAdmin === true", async () => { const updatedUser = await AppUserProfile.findOneAndUpdate( { @@ -72,12 +81,11 @@ describe("utilities -> adminCheck", () => { }, ); - const { adminCheck } = await import("../../src/utilities"); - await expect( adminCheck( updatedUser?.userId?.toString() ?? "", testOrganization ?? ({} as InterfaceOrganization), + false, ), ).resolves.not.toThrowError(); }); @@ -98,8 +106,6 @@ describe("utilities -> adminCheck", () => { }, ); - const { adminCheck } = await import("../../src/utilities"); - await expect( adminCheck( testUser?._id, @@ -108,14 +114,11 @@ describe("utilities -> adminCheck", () => { ).resolves.not.toThrowError(); }); it("throws error if user is not found with the specific Id", async () => { - const { requestContext } = await import("../../src/libraries"); - const spy = vi .spyOn(requestContext, "translate") .mockImplementationOnce((message) => `Translated ${message}`); try { - const { adminCheck } = await import("../../src/utilities"); await adminCheck( new mongoose.Types.ObjectId(), testOrganization ?? ({} as InterfaceOrganization), diff --git a/tests/utilities/auth.spec.ts b/tests/utilities/auth.spec.ts index b3c4d84a471..faef0f4d76b 100644 --- a/tests/utilities/auth.spec.ts +++ b/tests/utilities/auth.spec.ts @@ -2,7 +2,7 @@ import jwt from "jsonwebtoken"; import type mongoose from "mongoose"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import type { InterfaceAppUserProfile, InterfaceUser } from "../../src/models"; -import { AppUserProfile } from "../../src/models"; +import { AppUserProfile, Community } from "../../src/models"; import { createAccessToken, createRefreshToken, @@ -17,6 +17,7 @@ let user: TestUserType; let appUserProfile: TestAppUserProfileType; let MONGOOSE_INSTANCE: typeof mongoose; export interface InterfaceJwtTokenPayload { + timeout: number; tokenVersion: number; userId: string; firstName: string; @@ -37,8 +38,54 @@ afterAll(async () => { }); describe("createAccessToken", () => { + it("should use the timeout from the Community document if it exists", async () => { + // Set up a Community document with a specific timeout + const community = await Community.create({ + name: "Test Community", + timeout: 45, // Custom timeout in minutes + }); + + const token = await createAccessToken( + user ? user.toObject() : ({} as InterfaceUser), + appUserProfile + ? appUserProfile.toObject() + : ({} as InterfaceAppUserProfile), + ); + + expect(token).toBeDefined(); + + const decodedToken = jwt.decode(token); + + expect(decodedToken).not.toBeNull(); + expect((decodedToken as InterfaceJwtTokenPayload).timeout).toBe( + community.timeout, + ); + + // Clean up + await Community.deleteMany({}); + }); + + it("should default to 30 minutes if no Community document exists", async () => { + // Ensure no Community documents exist + await Community.deleteMany({}); + + const token = await createAccessToken( + user ? user.toObject() : ({} as InterfaceUser), + appUserProfile + ? appUserProfile.toObject() + : ({} as InterfaceAppUserProfile), + ); + + expect(token).toBeDefined(); + + const decodedToken = jwt.decode(token); + + expect(decodedToken).not.toBeNull(); + expect((decodedToken as InterfaceJwtTokenPayload).timeout).toBe(30); + }); + it("should create a JWT token with the correct payload", async () => { - const token = createAccessToken( + const token = await createAccessToken( user ? user.toObject() : ({} as InterfaceUser), appUserProfile ? appUserProfile.toObject() diff --git a/tests/utilities/checks.spec.ts b/tests/utilities/checks.spec.ts new file mode 100644 index 00000000000..6cf1f3744e5 --- /dev/null +++ b/tests/utilities/checks.spec.ts @@ -0,0 +1,156 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; +import { + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR, + EVENT_VOLUNTEER_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../src/constants"; + +import type { InterfaceUser } from "../../src/models"; +import { AppUserProfile, VolunteerMembership } from "../../src/models"; +import { connect, disconnect } from "../helpers/db"; +import type { TestUserType } from "../helpers/userAndOrg"; +import { requestContext } from "../../src/libraries"; +import { + checkAppUserProfileExists, + checkEventVolunteerExists, + checkUserExists, + checkVolunteerGroupExists, + checkVolunteerMembershipExists, +} from "../../src/utilities/checks"; +import { createTestUser } from "../helpers/user"; +import { createVolunteerAndActions } from "../helpers/volunteers"; +import type { TestEventVolunteerType } from "../helpers/events"; +import type { TestEventVolunteerGroupType } from "../resolvers/Mutation/createEventVolunteer.spec"; + +let randomUser: InterfaceUser; +let testUser: TestUserType; +let testVolunteer: TestEventVolunteerType; +let testGroup: TestEventVolunteerGroupType; +let MONGOOSE_INSTANCE: typeof mongoose; + +const expectError = async ( + fn: () => Promise, + expectedMessage: string, +): Promise => { + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => `Translated ${message}`); + + try { + await fn(); + } catch (error: unknown) { + expect((error as Error).message).toEqual(`Translated ${expectedMessage}`); + } + + expect(spy).toBeCalledWith(expectedMessage); +}; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + + const [, , user1, , volunteer1, , volunteerGroup] = + await createVolunteerAndActions(); + + testUser = user1; + testVolunteer = volunteer1; + testGroup = volunteerGroup; + + randomUser = (await createTestUser()) as InterfaceUser; + await AppUserProfile.deleteOne({ + userId: randomUser._id, + }); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("utilities -> checks", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("checkUserExists -> invalid userId", async () => { + await expectError( + () => checkUserExists(testUser?.appUserProfileId), + USER_NOT_FOUND_ERROR.MESSAGE, + ); + }); + + it("checkUserExists -> valid userId", async () => { + expect((await checkUserExists(testUser?._id))._id).toEqual(testUser?._id); + }); + + it("checkAppUserProfileExists -> unauthorized user", async () => { + await expectError( + () => checkAppUserProfileExists(randomUser), + USER_NOT_AUTHORIZED_ERROR.MESSAGE, + ); + }); + + it("checkAppUserProfileExists -> authorized user", async () => { + expect( + (await checkAppUserProfileExists(testUser as InterfaceUser)).userId, + ).toEqual(testUser?._id); + }); + + it("checkEventVolunteerExists -> invalid volunteerId", async () => { + await expectError( + () => checkEventVolunteerExists(testUser?._id), + EVENT_VOLUNTEER_NOT_FOUND_ERROR.MESSAGE, + ); + }); + + it("checkEventVolunteerExists -> valid volunteerId", async () => { + expect((await checkEventVolunteerExists(testVolunteer?._id))._id).toEqual( + testVolunteer?._id, + ); + }); + + it("checkVolunteerGroupExists -> invalid groupId", async () => { + await expectError( + () => checkVolunteerGroupExists(testUser?._id), + EVENT_VOLUNTEER_GROUP_NOT_FOUND_ERROR.MESSAGE, + ); + }); + + it("checkVolunteerGroupExists -> valid groupId", async () => { + expect((await checkVolunteerGroupExists(testGroup?._id))._id).toEqual( + testGroup?._id, + ); + }); + + it("checkVolunteerMembershipExists -> invalid membershipId", async () => { + await expectError( + () => checkVolunteerMembershipExists(testUser?._id), + EVENT_VOLUNTEER_MEMBERSHIP_NOT_FOUND_ERROR.MESSAGE, + ); + }); + + it("checkVolunteerMembershipExists -> valid membershipId", async () => { + const volunteerMembership = await VolunteerMembership.create({ + event: testVolunteer?._id, + volunteer: testUser?._id, + status: "invited", + }); + expect( + ( + await checkVolunteerMembershipExists( + volunteerMembership?._id.toString(), + ) + )._id, + ).toEqual(volunteerMembership?._id); + }); +}); diff --git a/tests/utilities/createSampleOrganizationUtil.spec.ts b/tests/utilities/createSampleOrganizationUtil.spec.ts index 6e33feab9ca..d2c3ccff079 100644 --- a/tests/utilities/createSampleOrganizationUtil.spec.ts +++ b/tests/utilities/createSampleOrganizationUtil.spec.ts @@ -114,7 +114,6 @@ describe("generatePostData function", () => { expect(post.title).toEqual(expect.any(String)); expect(post.creatorId).toEqual(expect.any(Object)); expect(post.organization).toEqual(expect.any(Object)); - expect(post.imageUrl).toEqual(expect.any(String)); expect(post.createdAt).toEqual(expect.any(Date)); }); diff --git a/tests/utilities/deletePreviousFile.spec.ts b/tests/utilities/deletePreviousFile.spec.ts new file mode 100644 index 00000000000..d48d2e8859a --- /dev/null +++ b/tests/utilities/deletePreviousFile.spec.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import type { InterfaceFile } from "../../src/models"; +import { File } from "../../src/models"; +import { deleteFile } from "../../src/REST/services/minio"; +import { BUCKET_NAME } from "../../src/config/minio"; +import { deletePreviousFile } from "../../src/utilities/encodedImageStorage/deletePreviousFile"; +import type { DeleteObjectCommandOutput } from "@aws-sdk/client-s3"; +import type { Document } from "mongoose"; + +type FileDocument = InterfaceFile & Document; + +vi.mock("../../src/models", () => ({ + File: { + findOne: vi.fn(), + deleteOne: vi.fn(), + findOneAndUpdate: vi.fn(), + }, +})); + +vi.mock("../../src/REST/services/minio", () => ({ + deleteFile: vi.fn(), +})); + +describe("deletePreviousFile", () => { + const mockFileId = "test-file-id"; + const mockObjectKey = "test-object-key"; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("should delete file and record when referenceCount is 1", async () => { + vi.mocked(File.findOne).mockResolvedValueOnce({ + referenceCount: 1, + } as FileDocument); + + vi.mocked(File.deleteOne).mockResolvedValueOnce({ + acknowledged: true, + deletedCount: 1, + }); + vi.mocked(deleteFile).mockResolvedValueOnce({ + $metadata: {}, + } as DeleteObjectCommandOutput); + + await deletePreviousFile(mockFileId, mockObjectKey); + + expect(File.findOne).toHaveBeenCalledWith({ + _id: mockFileId, + }); + + expect(deleteFile).toHaveBeenCalledWith(BUCKET_NAME, mockObjectKey); + + expect(File.deleteOne).toHaveBeenCalledWith({ + _id: mockFileId, + }); + + expect(File.findOneAndUpdate).not.toHaveBeenCalled(); + }); + + it("should decrement referenceCount when greater than 1", async () => { + vi.mocked(File.findOne).mockResolvedValueOnce({ + referenceCount: 2, + } as FileDocument); + + vi.mocked(File.findOneAndUpdate).mockResolvedValueOnce({ + referenceCount: 1, + } as FileDocument); + + await deletePreviousFile(mockFileId, mockObjectKey); + + expect(File.findOne).toHaveBeenCalledWith({ + _id: mockFileId, + }); + + expect(deleteFile).not.toHaveBeenCalled(); + + expect(File.deleteOne).not.toHaveBeenCalled(); + + expect(File.findOneAndUpdate).toHaveBeenCalledWith( + { + _id: mockFileId, + }, + { + $inc: { + referenceCount: -1, + }, + }, + ); + }); +}); diff --git a/tests/utilities/isValidMimeType.spec.ts b/tests/utilities/isValidMimeType.spec.ts new file mode 100644 index 00000000000..f93a10aee90 --- /dev/null +++ b/tests/utilities/isValidMimeType.spec.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from "vitest"; +import { isValidMimeType } from "../../src/utilities/isValidMimeType"; + +describe("isValidMimeType", () => { + it("should return true for valid image mime types", () => { + expect(isValidMimeType("image/jpeg")).toBe(true); + expect(isValidMimeType("image/png")).toBe(true); + expect(isValidMimeType("image/gif")).toBe(true); + expect(isValidMimeType("image/webp")).toBe(true); + }); + + it("should return true for valid video mime types", () => { + expect(isValidMimeType("video/mp4")).toBe(true); + }); + + it("should return false for invalid mime types", () => { + expect(isValidMimeType("image/bmp")).toBe(false); + expect(isValidMimeType("video/webm")).toBe(false); + expect(isValidMimeType("application/pdf")).toBe(false); + expect(isValidMimeType("")).toBe(false); + expect(isValidMimeType("invalid-mime-type")).toBe(false); + }); +}); diff --git a/tests/utilities/userTagsPaginationUtils.spec.ts b/tests/utilities/userTagsPaginationUtils.spec.ts new file mode 100644 index 00000000000..be910d02ba5 --- /dev/null +++ b/tests/utilities/userTagsPaginationUtils.spec.ts @@ -0,0 +1,350 @@ +import { describe, expect, it } from "vitest"; +import { + parseUserTagSortedBy, + parseUserTagMemberWhere, + parseUserTagWhere, + getUserTagGraphQLConnectionSort, + getUserTagMemberGraphQLConnectionFilter, + getUserTagGraphQLConnectionFilter, +} from "../../src/utilities/userTagsPaginationUtils"; +import type { SortedByOrder } from "../../src/types/generatedGraphQLTypes"; +import { Types } from "mongoose"; + +describe("parseUserTagWhere function", () => { + it("returns the failure state if name isn't provided", async () => { + const result = await parseUserTagWhere({}); + expect(result.isSuccessful).toEqual(false); + }); + + it("returns the failure state if provided name.starts_with isn't a string", async () => { + const result = await parseUserTagWhere({ + name: { + starts_with: Math.random() as unknown as string, + }, + }); + + expect(result.isSuccessful).toEqual(false); + }); + + it("returns the success state if where input is nullish", async () => { + const result = await parseUserTagWhere(undefined); + + expect(result.isSuccessful).toEqual(true); + }); + + it("returns the success state if provided name.starts_with is an empty string", async () => { + const result = await parseUserTagWhere({ + name: { + starts_with: "", + }, + }); + + expect(result.isSuccessful).toEqual(true); + }); +}); + +describe("parseUserTagMemberWhere function", () => { + it("returns the failure state if neither firstName nor lastName is provided", async () => { + const result = await parseUserTagMemberWhere({}); + + expect(result.isSuccessful).toEqual(false); + }); + + it("returns the failure state if firstName isn't a string", async () => { + const result = await parseUserTagMemberWhere({ + firstName: { starts_with: Math.random() as unknown as string }, + }); + + expect(result.isSuccessful).toEqual(false); + }); + + it("returns the failure state if lastName isn't a string", async () => { + const result = await parseUserTagMemberWhere({ + firstName: { starts_with: "firstName" }, + lastName: { starts_with: Math.random() as unknown as string }, + }); + + expect(result.isSuccessful).toEqual(false); + }); + + it("returns the success state if where input is nullish", async () => { + const result = await parseUserTagMemberWhere(undefined); + + expect(result.isSuccessful).toEqual(true); + }); + + it("returns the success state if provided firstName is provided and lastName isn't", async () => { + const result = await parseUserTagMemberWhere({ + firstName: { starts_with: "firstName" }, + }); + + expect(result.isSuccessful).toEqual(true); + }); + + it("returns the success state if provided lastName is provided and firstName isn't", async () => { + const result = await parseUserTagMemberWhere({ + lastName: { starts_with: "lastName" }, + }); + + expect(result.isSuccessful).toEqual(true); + }); + + it("returns the success state if provided names are non-empty and valid strings", async () => { + const result = await parseUserTagMemberWhere({ + firstName: { starts_with: "firstName" }, + lastName: { starts_with: "lastName" }, + }); + + expect(result.isSuccessful).toEqual(true); + }); +}); + +describe("parseUserTagSortedBy function", () => { + it("returns the failure state if provided sortedBy isn't of type SortedByOrder", async () => { + const result = await parseUserTagSortedBy({ + id: "" as unknown as SortedByOrder, + }); + + expect(result.isSuccessful).toEqual(false); + }); + + it("returns the success state if where input is nullish", async () => { + const result = await parseUserTagSortedBy(undefined); + + expect(result.isSuccessful).toEqual(true); + }); + + it("returns the success state if provided sort order is valid", async () => { + const result = await parseUserTagSortedBy({ + id: "ASCENDING", + }); + + expect(result.isSuccessful).toEqual(true); + }); +}); + +describe("getUserTagGraphQLConnectionFilter function", () => { + it(`when sort order is "ASCENDING" argument cursor is non-null and argument direction corresponds to backward`, async () => { + const cursor = "cursor"; + + expect( + getUserTagGraphQLConnectionFilter({ + cursor, + direction: "BACKWARD", + sortById: "ASCENDING", + nameStartsWith: "userName", + }), + ).toEqual({ + _id: { + $lt: cursor, + }, + name: { + $regex: /^userName/i, + }, + }); + }); + + it(`when sort order is "ASCENDING" argument cursor is non-null and argument direction corresponds to forward`, async () => { + const cursor = "cursor"; + + expect( + getUserTagGraphQLConnectionFilter({ + cursor, + direction: "FORWARD", + sortById: "ASCENDING", + nameStartsWith: "userName", + }), + ).toEqual({ + _id: { + $gt: cursor, + }, + name: { + $regex: /^userName/i, + }, + }); + }); + + it(`when sort order is "DESCENDING" argument cursor is non-null and argument direction corresponds to backward`, async () => { + const cursor = "cursor"; + + expect( + getUserTagGraphQLConnectionFilter({ + cursor, + direction: "BACKWARD", + sortById: "DESCENDING", + nameStartsWith: "userName", + }), + ).toEqual({ + _id: { + $gt: cursor, + }, + name: { + $regex: /^userName/i, + }, + }); + }); + + it(`when sort order is "DESCENDING" argument cursor is non-null and argument direction corresponds to forward`, async () => { + const cursor = "cursor"; + + expect( + getUserTagGraphQLConnectionFilter({ + cursor, + direction: "FORWARD", + sortById: "DESCENDING", + nameStartsWith: "userName", + }), + ).toEqual({ + _id: { + $lt: cursor, + }, + name: { + $regex: /^userName/i, + }, + }); + }); +}); + +describe("getUserTagMemberGraphQLConnectionFilter function", () => { + it(`when sort order is "ASCENDING" argument cursor is non-null and argument direction corresponds to backward`, async () => { + const cursor = new Types.ObjectId().toString(); + + expect( + getUserTagMemberGraphQLConnectionFilter({ + cursor, + direction: "BACKWARD", + sortById: "ASCENDING", + firstNameStartsWith: "firstName", + lastNameStartsWith: "lastName", + }), + ).toEqual({ + _id: { + $lt: new Types.ObjectId(cursor), + }, + firstName: { + $regex: /^firstName/i, + }, + lastName: { + $regex: /^lastName/i, + }, + }); + }); + + it(`when sort order is "ASCENDING" argument cursor is non-null and argument direction corresponds to forward`, async () => { + const cursor = new Types.ObjectId().toString(); + + expect( + getUserTagMemberGraphQLConnectionFilter({ + cursor, + direction: "FORWARD", + sortById: "ASCENDING", + firstNameStartsWith: "firstName", + lastNameStartsWith: "lastName", + }), + ).toEqual({ + _id: { + $gt: new Types.ObjectId(cursor), + }, + firstName: { + $regex: /^firstName/i, + }, + lastName: { + $regex: /^lastName/i, + }, + }); + }); + + it(`when sort order is "DESCENDING" argument cursor is non-null and argument direction corresponds to backward`, async () => { + const cursor = new Types.ObjectId().toString(); + + expect( + getUserTagMemberGraphQLConnectionFilter({ + cursor, + direction: "BACKWARD", + sortById: "DESCENDING", + firstNameStartsWith: "firstName", + lastNameStartsWith: "lastName", + }), + ).toEqual({ + _id: { + $gt: new Types.ObjectId(cursor), + }, + firstName: { + $regex: /^firstName/i, + }, + lastName: { + $regex: /^lastName/i, + }, + }); + }); + + it(`when sort order is "DESCENDING" argument cursor is non-null and argument direction corresponds to forward`, async () => { + const cursor = new Types.ObjectId().toString(); + + expect( + getUserTagMemberGraphQLConnectionFilter({ + cursor, + direction: "FORWARD", + sortById: "DESCENDING", + firstNameStartsWith: "firstName", + lastNameStartsWith: "lastName", + }), + ).toEqual({ + _id: { + $lt: new Types.ObjectId(cursor), + }, + firstName: { + $regex: /^firstName/i, + }, + lastName: { + $regex: /^lastName/i, + }, + }); + }); +}); + +describe("getUserTagGraphQLConnectionSort function", () => { + it(`when sort order is "ASCENDING" and argument direction corresponds to backward`, async () => { + expect( + getUserTagGraphQLConnectionSort({ + direction: "BACKWARD", + sortById: "ASCENDING", + }), + ).toEqual({ + _id: -1, + }); + }); + + it(`when sort order is "ASCENDING" and argument direction corresponds to forward`, async () => { + expect( + getUserTagGraphQLConnectionSort({ + direction: "FORWARD", + sortById: "ASCENDING", + }), + ).toEqual({ + _id: 1, + }); + }); + + it(`when sort order is "DESCENDING" and argument direction corresponds to backward`, async () => { + expect( + getUserTagGraphQLConnectionSort({ + direction: "BACKWARD", + sortById: "DESCENDING", + }), + ).toEqual({ + _id: 1, + }); + }); + + it(`when sort order is "DESCENDING" and argument direction corresponds to forward`, async () => { + expect( + getUserTagGraphQLConnectionSort({ + direction: "FORWARD", + sortById: "DESCENDING", + }), + ).toEqual({ + _id: -1, + }); + }); +});