diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 46e2d25b8c..22fd305608 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -13,5 +13,6 @@ reviews: drafts: false base_branches: - develop + - main chat: auto_reply: true \ No newline at end of file diff --git a/.dockerignore b/.dockerignore index 06d9a3ecb2..d0adaf2671 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 448f36a01a..fa6a0fabaa 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/.eslintrc.json b/.eslintrc.json index e0098cc7b9..6f13ff601d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,7 +24,6 @@ "@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", @@ -80,7 +79,7 @@ "plugins": ["@graphql-eslint"] }, { - "files": ["tests/**/*"], + "files": ["tests/**/*", "setup.ts"], "rules": { "no-restricted-imports": "off" } @@ -107,13 +106,13 @@ ], // 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/ban-types": "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", diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 493322305f..d9f95c0d65 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -31,4 +31,6 @@ If applicable, add screenshots to help explain your problem. Add any other context or screenshots about the feature request here. **Potential internship candidates** -Please read this if you are planning to apply for a Palisadoes Foundation internship https://github.com/PalisadoesFoundation/talawa/issues/359 + +Please read this if you are planning to apply for a Palisadoes Foundation internship +- https://github.com/PalisadoesFoundation/talawa/issues/359 diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 60d6401dcf..51aea0e9d9 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -23,4 +23,6 @@ A clear and concise description of approach to be followed. Add any other context or screenshots about the feature request here. **Potential internship candidates** -Please read this if you are planning to apply for a Palisadoes Foundation internship https://github.com/PalisadoesFoundation/talawa/issues/359 + +Please read this if you are planning to apply for a Palisadoes Foundation internship +- https://github.com/PalisadoesFoundation/talawa/issues/359 diff --git a/.github/workflows/check-tsdoc.js b/.github/workflows/check-tsdoc.js new file mode 100644 index 0000000000..c878389bdb --- /dev/null +++ b/.github/workflows/check-tsdoc.js @@ -0,0 +1,80 @@ +const fs = require('fs'); +const path = require('path'); + +// List of files to skip +const filesToSkip = [ + "app.ts", + "index.ts", + "constants.ts", + "db.ts", + "env.ts", + "logger.ts", + "getSort.ts", + // Add more files to skip as needed +]; + +// List of directories to skip +const dirsToSkip = [ + "typeDefs", + "services", + + // Add more directories to skip as needed +]; + +// Recursively find all .tsx files, excluding files listed in filesToSkip and directories in dirsToSkip +function findTsxFiles(dir) { + let results = []; + try { + const list = fs.readdirSync(dir); + list.forEach((file) => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + if (stat && stat.isDirectory()) { + // Skip directories in dirsToSkip + if (!dirsToSkip.includes(path.basename(filePath))) { + results = results.concat(findTsxFiles(filePath)); + } + } else if ( + filePath.endsWith('.ts') && + !filePath.endsWith('.spec.ts') && + !filesToSkip.includes(path.relative(dir, filePath)) + ) { + results.push(filePath); + } + }); + } catch (err) { + console.error(`Error reading directory ${dir}: ${err.message}`); + } + return results; +} + +// Check if a file contains at least one TSDoc comment +function containsTsDocComment(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + return /\/\*\*[\s\S]*?\*\//.test(content); + } catch (err) { + console.error(`Error reading file ${filePath}: ${err.message}`); + return false; + } +} + +// Main function to run the validation +function run() { + const dir = process.argv[2] || './src'; // Allow directory path as a command-line argument + const files = findTsxFiles(dir); + let allValid = true; + + files.forEach((file) => { + if (!containsTsDocComment(file)) { + console.error(`No TSDoc comment found in file: ${file}`); + allValid = false; + } + }); + + if (!allValid) { + process.exit(1); + } +} + +run(); \ 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 0000000000..95702064ee --- /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/md_mdx_format_adjuster.py b/.github/workflows/md_mdx_format_adjuster.py index c33ad1fa66..cd76a30cf6 100644 --- a/.github/workflows/md_mdx_format_adjuster.py +++ b/.github/workflows/md_mdx_format_adjuster.py @@ -12,7 +12,6 @@ 3) Pycodestyle 4) Flake8 """ - import os import argparse import re diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 0ad59c70a6..10fd9fd62f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -32,13 +32,16 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' - 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 + - name: Check for TSDoc comments + run: npm run check-tsdoc # Run the TSDoc check script + - name: Restore node_modules from cache id: cache-npm uses: actions/cache@v4 @@ -71,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 @@ -171,7 +174,7 @@ jobs: needs: [Code-Quality-Checks] strategy: matrix: - node-version: [20.x] + node-version: [22.x] services: mongo: image: mongo:4.4 @@ -216,7 +219,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' - name: Generate Access Token Secret run: echo "ACCESS_TOKEN_SECRET=$(openssl rand -hex 32)" >> $GITHUB_ENV diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 8eb1f501c1..90c6dfbcf7 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.x] + node-version: [22.x] services: redis: image: redis:6.0 @@ -58,7 +58,6 @@ jobs: # REFRESH_TOKEN_SECRET: ${{ secrets.REFRESH_TOKEN_SECRET }} # We checkout the content of the Talawa-API repository in a directory called `api` - # This is done as we will use the Talawa-Docs repository later too steps: - name: Checkout repository uses: actions/checkout@v4 @@ -94,101 +93,6 @@ jobs: name: '${{env.CODECOV_UNIQUE_NAME}}' - Generate-Documentation: - name: Generate Documentation - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/develop' - needs: Push-Workflow - steps: - - name: Checkout repository - uses: actions/checkout@v4 - # with: - # persist-credentials: false - - name: Generate Documentation of HTML pages - run: | - npm install --global typedoc - npm install typedoc-plugin-markdown - npm i --save-dev @types/node - npx typedoc --entryPoints src --out talawa-api-docs --plugin typedoc-plugin-markdown --theme markdown --entryPointStrategy expand - - - name: Make Markdown Files MDX Compatible - run: python3 .github/workflows/md_mdx_format_adjuster.py --directory talawa-api-docs - - - name: Checking doc updated - id: DocUpdated - run: | - if [ -n "$(git status --porcelain)" ]; then - echo "updateDoc=true" >> $GITHUB_OUTPUT - echo -e "Documentation has been updated!!" - else - Green='0;32' - NoColor='\033[0m' - echo -e "${Green}No documentation updated${NoColor}" - fi - - - name: Set env variables - if: steps.DocUpdated.outputs.updateDoc - run: | - echo "commit_id=$(echo $(git rev-parse HEAD))" >> $GITHUB_ENV - echo "email=$(echo $(git log --pretty=format:"%ae" $commit_id))" >> $GITHUB_ENV - - - name: Update Doc - if: steps.DocUpdated.outputs.updateDoc - run: | - Green='0;32' - NoColor='\033[0m' - git config --global user.name "${{github.actor}}" - git config --global user.email "${{env.email}}" - git add . - git commit -m "Update documentation" - git push -f https://$GH_TOKEN@github.com/PalisadoesFoundation/talawa-api.git HEAD:automated-docs - echo -e "🚀${Green} Hurrah! doc updated${NoColor}" - env: - ACCESS_TOKEN: ${{secrets.GH_TOKEN}} - - - name: Create Documentation Artifact - uses: actions/upload-artifact@v2 - with: - name: documentation-api - path: talawa-api-docs - - Empty-Commit: - name: Create Empty Commit - runs-on: ubuntu-latest - needs: Generate-Documentation - if: github.ref == 'refs/heads/develop' - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - token: ${{ secrets.TALAWA_DOCS_SYNC }} - - name: Empty Commit - run: | - git config --global user.name "${{github.actor}}" - git config --global user.email "${{env.email}}" - git config --global url.https://${{ secrets.TALAWA_DOCS_SYNC }}@github.com/.insteadOf https://github.com/ - git commit --allow-empty -m "Trigger Documentation Workflow" - git push origin develop:automated-docs --force - - # Copy-docs-to-talawa-docs: - # runs-on: ubuntu-latest - # if: github.ref == 'refs/heads/automated-docs' - # # needs: Generate-Documentation - # steps: - # - uses: actions/checkout@v3 - # - uses: dmnemec/copy_file_to_another_repo_action@v1.1.1 - # env: - # API_TOKEN_GITHUB: ${{secrets.TALAWA_DOCS_SYNC}} - # with: - # source_file: 'talawa-api-docs/' - # destination_repo: 'PalisadoesFoundation/talawa-docs' - # destination_branch: 'develop' - # destination_folder: 'docs/' - # user_email: '${{env.email}}' - # user_name: '${{github.actor}}' - # commit_message: 'Talawa API docs updated' - # You can find the deployment instructions in the scripts/cloud-api-demo/README.md file Deploy-Workflow: name: Deploying Application to Cloud VPS diff --git a/.gitignore b/.gitignore index e25629d39e..8ce4e0b129 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/.node-version b/.node-version index 790e1105f2..751f4c9f38 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v20.10.0 +v22.7.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac53c3f048..6e073e1886 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,6 +9,7 @@ If you are new to contributing to open source, please read the Open Source Guide - [Code of Conduct](#code-of-conduct) +- [Videos](#videos) - [Ways to Contribute](#ways-to-contribute) - [Our Development Process](#our-development-process) - [Issues](#issues) @@ -35,17 +36,17 @@ If you are new to contributing to open source, please read the Open Source Guide -## Videos - -1. Visit our [YouTube Channel playlists](https://www.youtube.com/@PalisadoesOrganization/playlists) for more insights - 1. The "[Getting Started - Developers](https://www.youtube.com/watch?v=YpBUoHxEeyg&list=PLv50qHwThlJUIzscg9a80a9-HmAlmUdCF&index=1)" videos are extremely helpful for new open source contributors. - ## Code of Conduct A safe environment is required for everyone to contribute. Read our [Code of Conduct Guide](CODE_OF_CONDUCT.md) to understand what this means. Let us know immediately if you have unacceptable experiences in this area. No one should fear voicing their opinion. Respones must be respectful. +## Videos + +1. Visit our [YouTube Channel playlists](https://www.youtube.com/@PalisadoesOrganization/playlists) for more insights + 1. The "[Getting Started - Developers](https://www.youtube.com/watch?v=YpBUoHxEeyg&list=PLv50qHwThlJUIzscg9a80a9-HmAlmUdCF&index=1)" videos are extremely helpful for new open source contributors. + ## Ways to Contribute If you are ready to start contributing code right away, get ready! diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000000..d83cfb3acd --- /dev/null +++ b/Caddyfile @@ -0,0 +1,5 @@ +api-demo.talawa.io { + reverse_proxy talawa-api-dev:4000 { + header_down Strict-Transport-Security max-age=31536000; + } +} diff --git a/Dockerfile.dev b/Dockerfile.dev index 2057c9d6cc..93e0564184 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,5 +1,5 @@ # Stage 1: Install Dependencies -FROM node:lts AS builder +FROM node:20.10.0 AS builder WORKDIR /usr/src/app @@ -9,13 +9,6 @@ RUN npm install COPY . . -# Stage 2: Final image -FROM node:lts-bookworm-slim - -WORKDIR /usr/src/app - -COPY --from=builder /usr/src/app ./ - EXPOSE 4000 CMD ["npm", "run", "dev"] diff --git a/INSTALLATION.md b/INSTALLATION.md index f57b890123..583dd18ace 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -29,6 +29,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 +59,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) @@ -102,6 +112,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 +143,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) @@ -317,16 +338,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 +508,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 +664,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. @@ -757,6 +885,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 diff --git a/README.md b/README.md index cfd37a47d2..bdf939c79f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Core features include: - [Talawa Components](#talawa-components) - [Documentation](#documentation) -- [Videos](#videos) + - [Videos](#videos) @@ -50,7 +50,7 @@ Core features include: 1. The `talawa` documentation can be found at our [docs.talawa.io](https://docs.talawa.io) site. 1. It is automatically generated from the markdown files stored in our [Talawa-Docs GitHub repository](https://github.com/PalisadoesFoundation/talawa-docs). This makes it easy for you to update our documenation. -# Videos +## Videos 1. Visit our [YouTube Channel playlists](https://www.youtube.com/@PalisadoesOrganization/playlists) for more insights 1. The "[Getting Started - Developers](https://www.youtube.com/watch?v=YpBUoHxEeyg&list=PLv50qHwThlJUIzscg9a80a9-HmAlmUdCF&index=1)" videos are extremely helpful for new open source contributors. diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index 02f8cbfa9a..a40aee1718 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" services: mongodb: @@ -7,31 +7,68 @@ 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 + - 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-container: + talawa-api-dev: build: context: . dockerfile: Dockerfile.dev - ports: - - "${SERVER_PORT:-4000}:${SERVER_PORT:-4000}" volumes: - .:/usr/src/app - /usr/src/app/node_modules depends_on: - mongodb - redis-stack-server + - minio environment: - MONGO_DB_URL=mongodb://mongodb:27017 - REDIS_HOST=redis-stack-server - REDIS_PORT=6379 + caddy: + image: caddy/caddy:2.2.1-alpine + container_name: caddy-service + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - $PWD/Caddyfile:/etc/caddy/Caddyfile + - $PWD/site:/srv + - caddy_data:/data + - caddy_config:/config + volumes: mongodb-data: 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 ef2448c104..08c49cdb82 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/package-lock.json b/package-lock.json index 172156bac0..350f57f1c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,16 +9,18 @@ "version": "1.0.0", "license": "GNU General Public License v3.0", "dependencies": { - "@apollo/server": "^4.10.4", - "@faker-js/faker": "^8.2.0", + "@apollo/server": "^4.11.0", + "@faker-js/faker": "^9.0.1", "@graphql-inspector/cli": "^5.0.6", "@graphql-tools/resolvers-composition": "^7.0.1", - "@graphql-tools/schema": "^10.0.4", + "@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", - "axios": "^1.7.2", + "@types/yargs": "^17.0.33", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.0.1", + "axios": "^1.7.4", "bcryptjs": "^2.4.3", "bluebird": "3.7.2", "cls-hooked": "^4.2.2", @@ -29,7 +31,7 @@ "dotenv": "^16.4.1", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^7.3.1", + "express-rate-limit": "^7.4.0", "graphql": "^16.9.0", "graphql-depth-limit": "^1.1.0", "graphql-scalars": "^1.20.1", @@ -46,19 +48,19 @@ "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "markdown-toc": "^1.2.0", - "mongodb": "^6.7.0", + "mongodb": "^6.8.0", "mongoose": "^8.3.2", - "mongoose-paginate-v2": "^1.8.2", + "mongoose-paginate-v2": "^1.8.3", "morgan": "^1.10.0", "nanoid": "^5.0.7", - "nodemailer": "^6.9.14", + "nodemailer": "^6.9.15", "pm2": "^5.4.0", - "redis": "^4.6.15", + "redis": "^4.7.0", "rrule": "^2.8.1", - "typedoc-plugin-markdown": "^4.2.1", + "typedoc-plugin-markdown": "^4.2.7", "uuid": "^10.0.0", "validator": "^13.12.0", - "winston": "^3.13.0", + "winston": "^3.14.2", "ws": "^8.18.0", "yargs": "^17.7.2", "zod": "^3.23.8", @@ -67,7 +69,7 @@ "devDependencies": { "@graphql-codegen/cli": "^5.0.2", "@graphql-codegen/typescript": "^4.0.9", - "@graphql-codegen/typescript-resolvers": "^4.2.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", @@ -79,39 +81,36 @@ "@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.6", + "@types/jsonwebtoken": "^9.0.7", + "@types/lodash": "^4.17.7", "@types/mongoose-paginate-v2": "^1.6.5", "@types/morgan": "^1.9.9", - "@types/node": "^20.14.9", + "@types/node": "^22.5.4", "@types/nodemailer": "^6.4.15", - "@types/uuid": "^9.0.7", + "@types/uuid": "^10.0.0", "@types/validator": "^13.12.0", - "@typescript-eslint/eslint-plugin": "^7.14.1", - "@typescript-eslint/parser": "^7.16.0", - "@vitest/coverage-v8": "^1.6.0", + "@vitest/coverage-v8": "^2.1.1", "cls-bluebird": "^2.1.0", - "concurrently": "^8.2.2", + "concurrently": "^9.0.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-tsdoc": "^0.3.0", "get-graphql-schema": "^2.1.2", - "graphql-markdown": "^7.0.0", - "husky": "^9.1.1", - "lint-staged": "^15.2.7", - "prettier": "^3.2.5", + "graphql-markdown": "^7.1.0", + "husky": "^9.1.5", + "lint-staged": "^15.2.10", + "prettier": "^3.3.3", "rimraf": "^6.0.1", - "tsx": "^4.16.2", - "typescript": "^5.4.5", - "vitest": "^1.2.1" + "tsx": "^4.19.1", + "typescript": "^5.5.4", + "vitest": "^2.0.5" } }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -161,9 +160,9 @@ } }, "node_modules/@apollo/server": { - "version": "4.10.4", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.10.4.tgz", - "integrity": "sha512-HS12CUa1wq8f5zKXOKJRwRdESFp4por9AINecpcsEUV9jsCP/NqPILgx0hCOOFJuKxmnaL7070xO6l5xmOq4Fw==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.11.0.tgz", + "integrity": "sha512-SWDvbbs0wl2zYhKG6aGLxwTJ72xpqp0awb2lotNpfezd9VcAvzaUizzKQqocephin2uMoaA8MguoyBmgtPzNWw==", "dependencies": { "@apollo/cache-control-types": "^1.0.3", "@apollo/server-gateway-interface": "^1.1.1", @@ -176,7 +175,6 @@ "@apollo/utils.usagereporting": "^2.1.0", "@apollo/utils.withrequired": "^2.0.0", "@graphql-tools/schema": "^9.0.0", - "@josephg/resolvable": "^1.0.0", "@types/express": "^4.17.13", "@types/express-serve-static-core": "^4.17.30", "@types/node-fetch": "^2.6.1", @@ -1633,9 +1631,9 @@ } }, "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==", + "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" ], @@ -1645,13 +1643,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1661,13 +1659,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1677,13 +1675,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", "cpu": [ "x64" ], @@ -1693,13 +1691,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", "cpu": [ "arm64" ], @@ -1709,13 +1707,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", "cpu": [ "x64" ], @@ -1725,13 +1723,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1741,13 +1739,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1757,13 +1755,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1773,13 +1771,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1789,13 +1787,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1805,13 +1803,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1821,13 +1819,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1837,13 +1835,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1853,13 +1851,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", "cpu": [ "riscv64" ], @@ -1869,13 +1867,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1885,13 +1883,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1901,13 +1899,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", "cpu": [ "x64" ], @@ -1917,13 +1915,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "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, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "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==", + "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" ], @@ -1933,13 +1947,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", "cpu": [ "x64" ], @@ -1949,13 +1963,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "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" ], @@ -1965,13 +1979,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "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==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", "cpu": [ "ia32" ], @@ -1981,13 +1995,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "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" ], @@ -1997,14 +2011,13 @@ "win32" ], "engines": { - "node": ">=12" + "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==", - "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -2019,7 +2032,6 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2028,7 +2040,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2051,7 +2062,6 @@ "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" @@ -2061,7 +2071,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2076,7 +2085,6 @@ "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" }, @@ -2088,7 +2096,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -2100,15 +2107,14 @@ "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": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "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==", + "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", @@ -2116,8 +2122,8 @@ } ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0", - "npm": ">=6.14.13" + "node": ">=18.0.0", + "npm": ">=9.0.0" } }, "node_modules/@graphql-codegen/add": { @@ -2326,14 +2332,14 @@ } }, "node_modules/@graphql-codegen/typescript-resolvers": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-4.2.0.tgz", - "integrity": "sha512-zy/H3T2RTT3PEkNS3CSdgF1lFrRsbHXu1WAIUhknmtsQugNBE2B5rbdPx8Wdat7QxtReSEEQ54Tgl9F87ecqyg==", + "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-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/typescript": "^4.0.8", - "@graphql-codegen/visitor-plugin-common": "5.3.0", + "@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" @@ -2343,9 +2349,9 @@ } }, "node_modules/@graphql-codegen/typescript-resolvers/node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.0.tgz", - "integrity": "sha512-+kUk7gRD/72Wfkjd7D96Lonh9k4lFw9d3O1+I07Jyja4QN9H42kdFEO0hM/b4Q9lLkI1yJ66Oym7lWz2Ikj3aw==", + "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-codegen/plugin-helpers": "^5.0.4", @@ -3712,11 +3718,11 @@ } }, "node_modules/@graphql-tools/merge": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.3.tgz", - "integrity": "sha512-FeKv9lKLMwqDu0pQjPpF59GY3HReUkWXKsMIuMuJQOKh9BETu7zPEFUELvcw8w+lwZkl4ileJsHXC9+AnsT2Lw==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.6.tgz", + "integrity": "sha512-TmkzFTFVieHnqu9mPTF6RxAQltaprpDQnM5HMTPSyMLXnJGMTvdWejV0yORKj7DW1YSi791/sUnKf8HytepBFQ==", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.5.4", "tslib": "^2.4.0" }, "engines": { @@ -3852,12 +3858,12 @@ } }, "node_modules/@graphql-tools/schema": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.4.tgz", - "integrity": "sha512-HuIwqbKxPaJujox25Ra4qwz0uQzlpsaBOzO6CVfzB/MemZdd+Gib8AIvfhQArK0YIN40aDran/yi+E5Xf0mQww==", + "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.3", - "@graphql-tools/utils": "^10.2.1", + "@graphql-tools/merge": "^9.0.6", + "@graphql-tools/utils": "^10.5.4", "tslib": "^2.4.0", "value-or-promise": "^1.0.12" }, @@ -3935,12 +3941,12 @@ "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" }, "node_modules/@graphql-tools/utils": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.3.2.tgz", - "integrity": "sha512-iaqOHS4f90KNADBHqVsRBjKpM6iSvsUg1q5GhWMK03loYLaDzftrEwcsl0OkSSnRhJvAsT7q4q3r3YzRoV0v1g==", + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.4.tgz", + "integrity": "sha512-XHnyCWSlg1ccsD8s0y6ugo5GZ5TpkTiFVNPSYms5G0s6Z/xTuSmiLBfeqgkfaCwLmLaQnRCmNDL2JRnqc2R5bQ==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", - "cross-inspect": "1.0.0", + "cross-inspect": "1.0.1", "dset": "^3.1.2", "tslib": "^2.4.0" }, @@ -3951,6 +3957,17 @@ "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" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@graphql-tools/wrap": { "version": "10.0.5", "resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.0.5.tgz", @@ -4026,7 +4043,6 @@ "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -4040,7 +4056,6 @@ "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" @@ -4050,7 +4065,6 @@ "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" }, @@ -4062,7 +4076,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "engines": { "node": ">=12.22" }, @@ -4074,8 +4087,7 @@ "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==", - "dev": true + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==" }, "node_modules/@ioredis/commands": { "version": "1.2.0", @@ -4187,23 +4199,6 @@ "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@josephg/resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", - "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -4234,9 +4229,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "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", @@ -5264,9 +5259,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.17", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.17.tgz", - "integrity": "sha512-IPvU9A31qRCZ7lds/x+ksuK/UMndd0EASveAvCvEtFFKIZjZ+m/a4a0L7S28KEWoR5ka8526hlSghDo4Hrc2Hg==", + "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", @@ -5285,25 +5280,25 @@ } }, "node_modules/@redis/json": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", - "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", + "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.1.6", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", - "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", + "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.0.5", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", - "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", + "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" } @@ -5314,9 +5309,9 @@ "integrity": "sha512-l3YHBLAol6d/IKnB9LhpD0cEZWAoe3eFKUyTYWmFmCO2Q/WOckxLQAUyMZWwZV2M/m3+4vgRoaolFqaII82/TA==" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", - "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -5327,9 +5322,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", - "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -5340,9 +5335,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", - "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -5353,9 +5348,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", - "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -5366,9 +5361,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", - "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -5379,9 +5374,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", - "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -5392,9 +5387,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", - "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -5405,9 +5400,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", - "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -5418,9 +5413,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", - "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -5431,9 +5426,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", - "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -5444,9 +5439,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", - "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -5457,9 +5452,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", - "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -5470,9 +5465,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", - "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -5483,9 +5478,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", - "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -5496,9 +5491,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", - "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -5509,9 +5504,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", - "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -5530,12 +5525,6 @@ "@types/hast": "^3.0.4" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -5626,9 +5615,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": { @@ -5750,9 +5739,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": "*" @@ -5787,10 +5776,11 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", - "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==", - "dev": true + "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" }, "node_modules/@types/long": { "version": "4.0.2", @@ -5822,11 +5812,11 @@ } }, "node_modules/@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/node-fetch": { @@ -5935,9 +5925,9 @@ "peer": true }, "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", "dev": true }, "node_modules/@types/validator": { @@ -5968,9 +5958,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": "*" } @@ -5981,31 +5971,30 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz", - "integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==", - "dev": true, + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.2.0.tgz", + "integrity": "sha512-02tJIs655em7fvt9gps/+4k4OsKULYGtLBPJfOsmOq1+3cdClYiF0+d6mHu6qDnTcg88wJBkcPLpQhq7FyDz0A==", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.14.1", - "@typescript-eslint/type-utils": "7.14.1", - "@typescript-eslint/utils": "7.14.1", - "@typescript-eslint/visitor-keys": "7.14.1", + "@typescript-eslint/scope-manager": "8.2.0", + "@typescript-eslint/type-utils": "8.2.0", + "@typescript-eslint/utils": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -6013,132 +6002,87 @@ } } }, - "node_modules/@typescript-eslint/parser": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", - "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", - "dev": true, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.2.0.tgz", + "integrity": "sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==", "dependencies": { - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/typescript-estree": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", - "debug": "^4.3.4" + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz", - "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0" - }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz", + "integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz", - "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==", - "dev": true, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz", + "integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "eslint-visitor-keys": "^3.4.3" + }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz", - "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==", - "dev": true, + "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/types": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@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", + "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz", - "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.16.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz", - "integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==", - "dev": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", + "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", "dependencies": { - "@typescript-eslint/types": "7.14.1", - "@typescript-eslint/visitor-keys": "7.14.1" + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -6146,53 +6090,47 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz", - "integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==", - "dev": true, + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.2.0.tgz", + "integrity": "sha512-g1CfXGFMQdT5S+0PSO0fvGXUaiSkl73U1n9LTK5aRAFnPlJ8dLKkXr4AaLFvPedW8lVDoMgLLE3JN98ZZfsj0w==", "dependencies": { - "@typescript-eslint/typescript-estree": "7.14.1", - "@typescript-eslint/utils": "7.14.1", + "@typescript-eslint/typescript-estree": "8.2.0", + "@typescript-eslint/utils": "8.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^8.56.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/types": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz", - "integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==", - "dev": true, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz", + "integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz", - "integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==", - "dev": true, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.2.0.tgz", + "integrity": "sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==", "dependencies": { - "@typescript-eslint/types": "7.14.1", - "@typescript-eslint/visitor-keys": "7.14.1", + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -6201,7 +6139,7 @@ "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -6213,51 +6151,196 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz", + "integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=10" - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", + "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", + "dependencies": { + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/@typescript-eslint/utils": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz", - "integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==", - "dev": true, + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.2.0.tgz", + "integrity": "sha512-O46eaYKDlV3TvAVDNcoDzd5N550ckSe8G4phko++OCSC1dYIb9LTc3HDGYdWqWIAT5qDUKphO6sd9RrpIJJPfg==", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.14.1", - "@typescript-eslint/types": "7.14.1", - "@typescript-eslint/typescript-estree": "7.14.1" + "@typescript-eslint/scope-manager": "8.2.0", + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/typescript-estree": "8.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.2.0.tgz", + "integrity": "sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz", + "integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.2.0.tgz", + "integrity": "sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz", + "integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz", - "integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==", - "dev": true, + "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==", "dependencies": { - "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/types": "8.0.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -6267,127 +6350,142 @@ "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==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@vitest/coverage-v8": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz", - "integrity": "sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.1.tgz", + "integrity": "sha512-md/A7A3c42oTT8JUHSqjP5uKTWJejzUW4jalpvs+rZ27gsURsMU8DEb+8Jf8C6Kj2gwfSHJqobDNBuoqlm0cFw==", "dev": true, "dependencies": { - "@ampproject/remapping": "^2.2.1", + "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", + "debug": "^4.3.6", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.11", + "magicast": "^0.3.4", + "std-env": "^3.7.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.6.0" + "@vitest/browser": "2.1.1", + "vitest": "2.1.1" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, "node_modules/@vitest/expect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", - "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz", + "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==", "dev": true, "dependencies": { - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "chai": "^4.3.10" + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", - "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "node_modules/@vitest/mocker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz", + "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==", "dev": true, "dependencies": { - "@vitest/utils": "1.6.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" }, "funding": { "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.1", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@vitest/pretty-format": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.1.tgz", + "integrity": "sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==", "dev": true, "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" + "tinyrainbow": "^1.2.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "node_modules/@vitest/runner": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.1.tgz", + "integrity": "sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==", "dev": true, - "engines": { - "node": ">=12.20" + "dependencies": { + "@vitest/utils": "2.1.1", + "pathe": "^1.1.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.1.tgz", + "integrity": "sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==", "dev": true, "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.1.1", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", - "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.1.tgz", + "integrity": "sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==", "dev": true, "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.1.tgz", + "integrity": "sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==", "dev": true, "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.1.1", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -6493,7 +6591,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -6505,20 +6602,10 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", @@ -6824,12 +6911,12 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/ast-types": { @@ -6930,9 +7017,9 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -7401,21 +7488,19 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "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": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -7483,15 +7568,12 @@ "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chokidar": { @@ -7832,8 +7914,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -7866,17 +7947,15 @@ } }, "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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.0.tgz", + "integrity": "sha512-iAxbsDeUkn8E/4+QalT7T3WvlyTfmsoez+19lbbcsxZdOEMfBukd8LA30KYez2UR5xkKFzbcqXIZy5RisCbaxw==", "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" @@ -7886,28 +7965,12 @@ "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": "^14.13.0 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "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==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -8183,9 +8246,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dependencies": { "ms": "2.1.2" }, @@ -8214,13 +8277,10 @@ "dev": true }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "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": { - "type-detect": "^4.0.0" - }, "engines": { "node": ">=6" } @@ -8228,8 +8288,7 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/defaults": { "version": "1.0.4", @@ -8368,15 +8427,6 @@ "node": ">=0.8.0" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -8392,7 +8442,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8518,6 +8567,18 @@ "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==", + "dev": true, + "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", @@ -8658,41 +8719,43 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "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": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@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": { @@ -8752,7 +8815,6 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8949,7 +9011,6 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -8965,7 +9026,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -8977,7 +9037,6 @@ "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" @@ -8987,7 +9046,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -9002,7 +9060,6 @@ "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" }, @@ -9014,7 +9071,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -9026,7 +9082,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -9055,7 +9110,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -9067,7 +9121,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -9203,9 +9256,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.3.1.tgz", - "integrity": "sha512-BbaryvkY4wEgDqLgD18/NSy2lDO2jTuT9Y8c1Mpx0X63Yz0sYd5zN6KPe7UvpuSVvV33T6RaE1o1IVZQjHMYgw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.0.tgz", + "integrity": "sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==", "engines": { "node": ">= 16" }, @@ -9335,8 +9388,7 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fast-printf": { "version": "1.6.9", @@ -9440,7 +9492,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -9518,7 +9569,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -9534,7 +9584,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -9548,7 +9597,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -9562,8 +9610,7 @@ "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, "node_modules/fn.name": { "version": "1.1.0", @@ -9683,8 +9730,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -9972,7 +10018,6 @@ "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", @@ -9992,7 +10037,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -10004,7 +10048,6 @@ "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" @@ -10014,7 +10057,6 @@ "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" }, @@ -10083,8 +10125,7 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/graphql": { "version": "16.9.0", @@ -10163,9 +10204,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.1.0", + "resolved": "https://registry.npmjs.org/graphql-markdown/-/graphql-markdown-7.1.0.tgz", + "integrity": "sha512-S7wff3Fy5deUSfGB7J+IYO3mygulQQSpHoSnhq3GlD6q/oYGuCfRyuQqCivtHI0yHnPVM33c/VGF7pbCzvsNJg==", "dev": true, "dependencies": { "deep-diff": "^1.0.2", @@ -10621,9 +10662,9 @@ } }, "node_modules/husky": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.1.tgz", - "integrity": "sha512-fCqlqLXcBnXa/TJXmT93/A36tJsjdJkibQ1MuIiFyCCYUlpYpIaj2mv1w+3KR6Rzu1IC3slFTje5f6DUp2A2rg==", + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.5.tgz", + "integrity": "sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==", "dev": true, "bin": { "husky": "bin.js" @@ -10752,7 +10793,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -10770,7 +10810,6 @@ "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" @@ -11132,7 +11171,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -11389,9 +11427,9 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", - "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", @@ -11513,8 +11551,7 @@ "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==", - "dev": true + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -11552,8 +11589,7 @@ "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -11584,12 +11620,6 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -11709,7 +11739,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -11753,7 +11782,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -11763,9 +11791,9 @@ } }, "node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true, "engines": { "node": ">=14" @@ -11789,21 +11817,21 @@ } }, "node_modules/lint-staged": { - "version": "15.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.7.tgz", - "integrity": "sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==", + "version": "15.2.10", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz", + "integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==", "dev": true, "dependencies": { "chalk": "~5.3.0", "commander": "~12.1.0", - "debug": "~4.3.4", + "debug": "~4.3.6", "execa": "~8.0.1", - "lilconfig": "~3.1.1", - "listr2": "~8.2.1", - "micromatch": "~4.0.7", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.8", "pidtree": "~0.6.0", "string-argv": "~0.3.2", - "yaml": "~2.4.2" + "yaml": "~2.5.0" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -11816,12 +11844,15 @@ } }, "node_modules/lint-staged/node_modules/ansi-escapes": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", - "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11864,15 +11895,15 @@ } }, "node_modules/lint-staged/node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11913,16 +11944,16 @@ } }, "node_modules/lint-staged/node_modules/listr2": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz", - "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", "dev": true, "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", - "log-update": "^6.0.0", - "rfdc": "^1.3.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" }, "engines": { @@ -11930,14 +11961,14 @@ } }, "node_modules/lint-staged/node_modules/log-update": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", - "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "dependencies": { - "ansi-escapes": "^6.2.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^7.0.0", + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" }, @@ -11979,52 +12010,37 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/lint-staged/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lint-staged/node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/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==", - "dev": true - }, "node_modules/lint-staged/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -12042,9 +12058,9 @@ } }, "node_modules/lint-staged/node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "dependencies": { "emoji-regex": "^10.3.0", @@ -12148,27 +12164,10 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -12238,8 +12237,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/lodash.once": { "version": "4.1.1", @@ -12371,9 +12369,9 @@ } }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "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" @@ -12412,26 +12410,23 @@ "peer": true }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magicast": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.3.tgz", - "integrity": "sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "source-map-js": "^1.0.2" + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" } }, "node_modules/make-dir": { @@ -12633,9 +12628,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -12686,6 +12681,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -12751,27 +12758,15 @@ "node": ">=10" } }, - "node_modules/mlly": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", - "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", - "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "ufo": "^1.3.2" - } - }, "node_modules/module-details-from-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" }, "node_modules/mongodb": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz", - "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz", + "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", "bson": "^6.7.0", @@ -12844,9 +12839,9 @@ } }, "node_modules/mongoose-paginate-v2": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mongoose-paginate-v2/-/mongoose-paginate-v2-1.8.2.tgz", - "integrity": "sha512-T/Z3qKyKnPUa6UkH1IjHxdYnYApCAKk9zb2C0GF5hg3QETcI62AUAUQGCBE2tIw7fF4feUaDARMajj/bersyvg==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/mongoose-paginate-v2/-/mongoose-paginate-v2-1.8.3.tgz", + "integrity": "sha512-Fkg9amsmtRkqUQ19kwOvw6XodkqXoySr82MkV5XPeD+1u/m68tWnDcz3xRwDPzlIsAr1ctx/4Rz/bnfD3PIJqQ==", "engines": { "node": ">=4.0.0" } @@ -13002,8 +12997,7 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/needle": { "version": "2.4.0", @@ -13119,9 +13113,10 @@ "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.15", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } @@ -13334,7 +13329,6 @@ "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" } @@ -13382,7 +13376,6 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -13445,7 +13438,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -13605,7 +13597,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -13614,7 +13605,6 @@ "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" } @@ -13698,12 +13688,12 @@ "dev": true }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/peek-readable": { @@ -13724,9 +13714,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -13762,17 +13752,6 @@ "node": ">=10" } }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", - "dev": true, - "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" - } - }, "node_modules/pm2": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.4.0.tgz", @@ -13951,9 +13930,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -13971,8 +13950,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -14000,15 +13979,14 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -14020,32 +13998,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -14360,16 +14312,16 @@ } }, "node_modules/redis": { - "version": "4.6.15", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.15.tgz", - "integrity": "sha512-2NtuOpMW3tnYzBw6S8mbXSX7RPzvVFCA2wFJq9oErushO2UeBkxObk+uvo7gv7n0rhWeOj/IzrHO8TjcFlRSOg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.17", + "@redis/client": "1.6.0", "@redis/graph": "1.1.1", - "@redis/json": "1.0.6", - "@redis/search": "1.1.6", - "@redis/time-series": "1.0.5" + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" } }, "node_modules/redis-errors": { @@ -14680,9 +14632,9 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, "node_modules/rimraf": { @@ -14743,9 +14695,9 @@ } }, "node_modules/rollup": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", - "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -14758,25 +14710,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.17.2", - "@rollup/rollup-android-arm64": "4.17.2", - "@rollup/rollup-darwin-arm64": "4.17.2", - "@rollup/rollup-darwin-x64": "4.17.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", - "@rollup/rollup-linux-arm-musleabihf": "4.17.2", - "@rollup/rollup-linux-arm64-gnu": "4.17.2", - "@rollup/rollup-linux-arm64-musl": "4.17.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", - "@rollup/rollup-linux-riscv64-gnu": "4.17.2", - "@rollup/rollup-linux-s390x-gnu": "4.17.2", - "@rollup/rollup-linux-x64-gnu": "4.17.2", - "@rollup/rollup-linux-x64-musl": "4.17.2", - "@rollup/rollup-win32-arm64-msvc": "4.17.2", - "@rollup/rollup-win32-ia32-msvc": "4.17.2", - "@rollup/rollup-win32-x64-msvc": "4.17.2", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, + "node_modules/rollup/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==", + "dev": true + }, "node_modules/rrule": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", @@ -15259,9 +15217,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" @@ -15292,12 +15250,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", @@ -15553,7 +15505,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -15561,24 +15512,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", - "dev": true, - "dependencies": { - "js-tokens": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", - "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", - "dev": true - }, "node_modules/strtok3": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", @@ -15663,39 +15596,74 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/test-exclude/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/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/test-exclude/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/test-exclude/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-hex": { @@ -15706,8 +15674,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/through": { "version": "2.3.8", @@ -15725,24 +15692,39 @@ } }, "node_modules/tinybench": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", - "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", "dev": true }, "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "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" @@ -15896,7 +15878,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, "engines": { "node": ">=16" }, @@ -15940,12 +15921,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsx": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.16.2.tgz", - "integrity": "sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==", + "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.21.5", + "esbuild": "~0.23.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -15995,7 +15976,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -16003,15 +15983,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -16137,9 +16108,9 @@ } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.1.tgz", - "integrity": "sha512-7hQt/1WaW/VI4+x3sxwcCGsEylP1E1GvF6OTTELK5sfTEp6AeK+83jkCOgZGp1pI2DiOammMYQMnxxOny9TKsQ==", + "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" }, @@ -16148,9 +16119,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16188,12 +16159,6 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "peer": true }, - "node_modules/ufo": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", - "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", - "dev": true - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -16219,9 +16184,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "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", @@ -16398,14 +16363,14 @@ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/vite": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", - "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", + "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -16424,6 +16389,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -16441,6 +16407,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -16453,15 +16422,14 @@ } }, "node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.1.tgz", + "integrity": "sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", + "debug": "^4.3.6", + "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { @@ -16475,9 +16443,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "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" ], @@ -16491,9 +16459,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "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" ], @@ -16507,9 +16475,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "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" ], @@ -16523,9 +16491,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -16539,9 +16507,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "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" ], @@ -16555,9 +16523,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "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" ], @@ -16571,9 +16539,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "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" ], @@ -16587,9 +16555,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "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" ], @@ -16603,9 +16571,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -16619,9 +16587,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "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" ], @@ -16635,9 +16603,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -16651,9 +16619,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "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" ], @@ -16667,9 +16635,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -16683,9 +16651,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "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" ], @@ -16699,9 +16667,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "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" ], @@ -16715,9 +16683,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -16731,9 +16699,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "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" ], @@ -16747,9 +16715,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -16763,9 +16731,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -16779,9 +16747,9 @@ } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "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" ], @@ -16795,9 +16763,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -16811,9 +16779,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "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" ], @@ -16827,9 +16795,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -16843,9 +16811,9 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -16855,57 +16823,56 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", - "dev": true, - "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz", + "integrity": "sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.1", + "@vitest/mocker": "2.1.1", + "@vitest/pretty-format": "^2.1.1", + "@vitest/runner": "2.1.1", + "@vitest/snapshot": "2.1.1", + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "1.6.0", - "why-is-node-running": "^2.2.2" + "vite-node": "2.1.1", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -16919,8 +16886,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", + "@vitest/browser": "2.1.1", + "@vitest/ui": "2.1.1", "happy-dom": "*", "jsdom": "*" }, @@ -17082,9 +17049,9 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "dependencies": { "siginfo": "^2.0.0", @@ -17098,15 +17065,15 @@ } }, "node_modules/winston": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", - "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.2.tgz", + "integrity": "sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.4.0", + "logform": "^2.6.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", @@ -17211,8 +17178,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.18.0", @@ -17256,9 +17222,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", - "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index f634b418d5..cd9c53ef9c 100644 --- a/package.json +++ b/package.json @@ -6,17 +6,21 @@ "scripts": { "build": "tsc --pretty --project tsconfig.build.json", "dev": "concurrently \"tsx --watch ./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\" \"tsx --watch ./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 forks --poolOptions.forks.singleFork --coverage", + "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}\"", "format:check": "prettier --check \"**/*.{ts,tsx,json,scss,css}\"", + "check-tsdoc": "node .github/workflows/check-tsdoc.js", "prepare": "husky", "generate:graphql-markdown": "graphql-markdown http://localhost:4000/graphql > docs/Schema.md", "generate:graphql-schema": "get-graphql-schema http://localhost:4000/graphql --json > docs/schema.json", @@ -43,16 +47,18 @@ }, "homepage": "https://github.com/PalisadoesFoundation/talawa-api#readme", "dependencies": { - "@apollo/server": "^4.10.4", - "@faker-js/faker": "^8.2.0", + "@apollo/server": "^4.11.0", + "@faker-js/faker": "^9.0.1", "@graphql-inspector/cli": "^5.0.6", "@graphql-tools/resolvers-composition": "^7.0.1", - "@graphql-tools/schema": "^10.0.4", + "@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", - "axios": "^1.7.2", + "@types/yargs": "^17.0.33", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.0.1", + "axios": "^1.7.4", "bcryptjs": "^2.4.3", "bluebird": "3.7.2", "cls-hooked": "^4.2.2", @@ -63,7 +69,7 @@ "dotenv": "^16.4.1", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", - "express-rate-limit": "^7.3.1", + "express-rate-limit": "^7.4.0", "graphql": "^16.9.0", "graphql-depth-limit": "^1.1.0", "graphql-scalars": "^1.20.1", @@ -80,19 +86,19 @@ "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "markdown-toc": "^1.2.0", - "mongodb": "^6.7.0", + "mongodb": "^6.8.0", "mongoose": "^8.3.2", - "mongoose-paginate-v2": "^1.8.2", + "mongoose-paginate-v2": "^1.8.3", "morgan": "^1.10.0", "nanoid": "^5.0.7", - "nodemailer": "^6.9.14", + "nodemailer": "^6.9.15", "pm2": "^5.4.0", - "redis": "^4.6.15", + "redis": "^4.7.0", "rrule": "^2.8.1", - "typedoc-plugin-markdown": "^4.2.1", + "typedoc-plugin-markdown": "^4.2.7", "uuid": "^10.0.0", "validator": "^13.12.0", - "winston": "^3.13.0", + "winston": "^3.14.2", "ws": "^8.18.0", "yargs": "^17.7.2", "zod": "^3.23.8", @@ -101,7 +107,7 @@ "devDependencies": { "@graphql-codegen/cli": "^5.0.2", "@graphql-codegen/typescript": "^4.0.9", - "@graphql-codegen/typescript-resolvers": "^4.2.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", @@ -113,32 +119,30 @@ "@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.6", + "@types/jsonwebtoken": "^9.0.7", + "@types/lodash": "^4.17.7", "@types/mongoose-paginate-v2": "^1.6.5", "@types/morgan": "^1.9.9", - "@types/node": "^20.14.9", + "@types/node": "^22.5.4", "@types/nodemailer": "^6.4.15", - "@types/uuid": "^9.0.7", + "@types/uuid": "^10.0.0", "@types/validator": "^13.12.0", - "@typescript-eslint/eslint-plugin": "^7.14.1", - "@typescript-eslint/parser": "^7.16.0", - "@vitest/coverage-v8": "^1.6.0", + "@vitest/coverage-v8": "^2.1.1", "cls-bluebird": "^2.1.0", - "concurrently": "^8.2.2", + "concurrently": "^9.0.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-tsdoc": "^0.3.0", "get-graphql-schema": "^2.1.2", - "graphql-markdown": "^7.0.0", - "husky": "^9.1.1", - "lint-staged": "^15.2.7", - "prettier": "^3.2.5", + "graphql-markdown": "^7.1.0", + "husky": "^9.1.5", + "lint-staged": "^15.2.10", + "prettier": "^3.3.3", "rimraf": "^6.0.1", - "tsx": "^4.16.2", - "typescript": "^5.4.5", - "vitest": "^1.2.1" + "tsx": "^4.19.1", + "typescript": "^5.5.4", + "vitest": "^2.0.5" }, "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 0000000000..ed2278ee1b 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 0000000000..9864a3a13d Binary files /dev/null and b/public/markdown/images/mino-webui-login.png differ diff --git a/sample_data/agendaCategories.json b/sample_data/agendaCategories.json new file mode 100644 index 0000000000..ef1b705cb1 --- /dev/null +++ b/sample_data/agendaCategories.json @@ -0,0 +1,46 @@ +[ + { + "_id": "66dc0ef89eb305badd7f5af6", + "name": "Default", + "description": "Default action item category", + "organizationId": "6437904485008f171cf29924", + "createdBy": "64378abd85008f171cf2990d", + "updatedBy": "64378abd85008f171cf2990d", + "createdAt": "2024-01-30T05:16:52.827Z", + "updatedAt": "2024-01-30T05:16:52.827Z", + "__v": 0 + }, + { + "_id": "66dc0ef89eb305badd7f5af7", + "name": "Default", + "description": "Default action item category", + "organizationId": "6537904485008f171cf29924", + "createdBy": "64378abd85008f171cf2990d", + "updatedBy": "64378abd85008f171cf2990d", + "createdAt": "2024-01-30T05:16:52.827Z", + "updatedAt": "2024-01-30T05:16:52.827Z", + "__v": 0 + }, + { + "_id": "66dc0ef89eb305badd7f5af8", + "name": "Default", + "description": "Default action item category", + "organizationId": "6637904485008f171cf29924", + "createdBy": "64378abd85008f171cf2990d", + "updatedBy": "64378abd85008f171cf2990d", + "createdAt": "2024-01-30T05:16:52.827Z", + "updatedAt": "2024-01-30T05:16:52.827Z", + "__v": 0 + }, + { + "_id": "66dc0ef89eb305badd7f5af9", + "name": "Default", + "description": "Default action item category", + "organizationId": "6737904485008f171cf29924", + "createdBy": "64378abd85008f171cf2990d", + "updatedBy": "64378abd85008f171cf2990d", + "createdAt": "2024-01-30T05:16:52.827Z", + "updatedAt": "2024-01-30T05:16:52.827Z", + "__v": 0 + } +] diff --git a/schema.graphql b/schema.graphql index f605000c6a..c5fb18a0dd 100644 --- a/schema.graphql +++ b/schema.graphql @@ -5,6 +5,7 @@ directive @role(requires: UserType) on FIELD_DEFINITION type ActionItem { _id: ID! actionItemCategory: ActionItemCategory + allotedHours: Float assignee: User assigner: User assignmentDate: Date! @@ -29,16 +30,24 @@ type ActionItemCategory { updatedAt: Date! } +input ActionItemCategoryWhereInput { + is_disabled: Boolean + name_contains: String +} + input ActionItemWhereInput { actionItemCategory_id: ID + assigneeName: String + categoryName: String event_id: ID - is_active: Boolean is_completed: Boolean } enum ActionItemsOrderByInput { createdAt_ASC createdAt_DESC + dueDate_ASC + dueDate_DESC } type Address { @@ -148,10 +157,12 @@ type AppUserProfile { _id: ID! adminFor: [Organization] appLanguageCode: String! + campaigns: [FundraisingCampaign] createdEvents: [Event] createdOrganizations: [Organization] eventAdmin: [Event] isSuperAdmin: Boolean! + pledges: [FundraisingCampaignPledge] pluginCreationAllowed: Boolean! userId: User! } @@ -173,7 +184,10 @@ enum CampaignOrderByInput { } input CampaignWhereInput { + fundId: ID + id: ID name_contains: String + organizationId: ID } type CheckIn { @@ -264,6 +278,7 @@ interface ConnectionPageInfo { scalar CountryCode input CreateActionItemInput { + allotedHours: Float assigneeId: ID! dueDate: Date eventId: ID @@ -743,6 +758,12 @@ input EventVolunteerGroupInput { volunteersRequired: Int } +input EventVolunteerGroupWhereInput { + eventId: ID + name_contains: String + volunteerId: ID +} + input EventVolunteerInput { eventId: ID! groupId: ID! @@ -840,6 +861,7 @@ input FundCampaignInput { fundId: ID! fundingGoal: Float! name: String! + organizationId: ID! startDate: Date! } @@ -878,6 +900,7 @@ type FundraisingCampaign { fundId: Fund! fundingGoal: Float! name: String! + organizationId: Organization! pledges: [FundraisingCampaignPledge] startDate: Date! updatedAt: DateTime! @@ -886,7 +909,7 @@ type FundraisingCampaign { type FundraisingCampaignPledge { _id: ID! amount: Float! - campaigns: [FundraisingCampaign]! + campaign: FundraisingCampaign! currency: Currency! endDate: Date startDate: Date @@ -1067,7 +1090,7 @@ type Mutation { checkIn(data: CheckInCheckOutInput!): CheckIn! checkOut(data: CheckInCheckOutInput!): CheckOut! createActionItem(actionItemCategoryId: ID!, data: CreateActionItemInput!): ActionItem! - createActionItemCategory(name: String!, organizationId: ID!): ActionItemCategory! + createActionItemCategory(isDisabled: Boolean!, name: String!, organizationId: ID!): ActionItemCategory! createAdmin(data: UserAndOrganizationInput!): CreateAdminPayload! createAdvertisement(input: CreateAdvertisementInput!): CreateAdvertisementPayload createAgendaCategory(input: CreateAgendaCategoryInput!): AgendaCategory! @@ -1124,8 +1147,6 @@ type Mutation { removeEventAttendee(data: EventAttendeeInput!): User! removeEventVolunteer(id: ID!): EventVolunteer! removeEventVolunteerGroup(id: ID!): EventVolunteerGroup! - removeFund(id: ID!): Fund! - removeFundraisingCampaign(id: ID!): FundraisingCampaign! removeFundraisingCampaignPledge(id: ID!): FundraisingCampaignPledge! removeGroupChat(chatId: ID!): GroupChat! removeMember(data: UserAndOrganizationInput!): Organization! @@ -1336,6 +1357,13 @@ enum PledgeOrderByInput { startDate_DESC } +input PledgeWhereInput { + campaignId: ID + firstName_contains: String + id: ID + name_contains: String +} + type Plugin { _id: ID! pluginCreatedBy: String! @@ -1456,9 +1484,9 @@ type PostsConnection { } type Query { - actionItemCategoriesByOrganization(organizationId: ID!): [ActionItemCategory] + actionItemCategoriesByOrganization(orderBy: ActionItemsOrderByInput, organizationId: ID!, where: ActionItemCategoryWhereInput): [ActionItemCategory] actionItemsByEvent(eventId: ID!): [ActionItem] - actionItemsByOrganization(orderBy: ActionItemsOrderByInput, organizationId: ID!, where: ActionItemWhereInput): [ActionItem] + actionItemsByOrganization(eventId: ID, orderBy: ActionItemsOrderByInput, organizationId: ID!, where: ActionItemWhereInput): [ActionItem] adminPlugin(orgId: ID!): [Plugin] advertisementsConnection(after: String, before: String, first: PositiveInt, last: PositiveInt): AdvertisementsConnection agendaCategory(id: ID!): AgendaCategory! @@ -1487,11 +1515,15 @@ type Query { getEventAttendee(eventId: ID!, userId: ID!): EventAttendee getEventAttendeesByEventId(eventId: ID!): [EventAttendee] getEventInvitesByUserId(userId: ID!): [EventAttendee!]! + getEventVolunteerGroups(where: EventVolunteerGroupWhereInput): [EventVolunteerGroup]! getFundById(id: ID!, orderBy: CampaignOrderByInput, where: CampaignWhereInput): Fund! - getFundraisingCampaignById(id: ID!, orderBy: PledgeOrderByInput): FundraisingCampaign! 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] + getUserTag(id: ID!): UserTag + getUserTagAncestors(id: ID!): [UserTag] getVenueByOrgId(first: Int, orderBy: VenueOrderByInput, orgId: ID!, skip: Int, where: VenueWhereInput): [Venue] getlanguage(lang_code: String!): [Translation] groupChatById(id: ID!): GroupChat @@ -1550,24 +1582,24 @@ enum RecurringEventMutationType { } type SocialMediaUrls { + X: String facebook: String gitHub: String instagram: String linkedIn: String reddit: String slack: String - twitter: String youTube: String } input SocialMediaUrlsInput { + X: String facebook: String gitHub: String instagram: String linkedIn: String reddit: String slack: String - twitter: String youTube: String } @@ -1637,6 +1669,7 @@ input UpdateActionItemCategoryInput { } input UpdateActionItemInput { + allotedHours: Float assigneeId: ID completionDate: Date dueDate: Date @@ -1777,9 +1810,9 @@ input UpdateUserPasswordInput { } input UpdateUserTagInput { - _id: ID! name: String! - tagColor: String! + tagColor: String + tagId: ID! } scalar Upload @@ -1796,6 +1829,7 @@ type User { eventAdmin: [Event] firstName: String! gender: Gender + identifier: Int! image: String joinedOrganizations: [Organization] lastName: String! @@ -1917,7 +1951,7 @@ type UserTag { type UserTagsConnection { edges: [UserTagsConnectionEdge!]! pageInfo: DefaultConnectionPageInfo! - totalCount: PositiveInt + totalCount: Int } """A default connection edge on the UserTag type for UserTagsConnection.""" @@ -1965,6 +1999,7 @@ input UserWhereInput { type UsersConnection { edges: [UsersConnectionEdge!]! pageInfo: DefaultConnectionPageInfo! + totalCount: Int } """A default connection edge on the User type for UsersConnection.""" diff --git a/scripts/cloud-api-demo/README.md b/scripts/cloud-api-demo/README.md index 4617ad3178..7345054cfe 100644 --- a/scripts/cloud-api-demo/README.md +++ b/scripts/cloud-api-demo/README.md @@ -7,33 +7,26 @@ This guide provides step-by-step instructions for setting up a cloud instance of - You have sudo privileges. - You are executing all commands under the home directory of the 'talawa-api' user. -# Table Of Contents - -- [Talawa API Cloud Instance Setup Guide](#talawa-api-cloud-instance-setup-guide) - - [1. Virtual Private Server (VPS) Setup](#1-virtual-private-server-vps-setup) - - [2. Repository Setup](#2-repository-setup) - - [3. Docker Configuration](#3-docker-configuration) - - [4. Running the Containers](#4-running-the-containers) - - [5. Firewall Setup](#5-firewall-setup) - - [6. NGINX Installation and Configuration](#6-nginx-installation-and-configuration) - - [6.1 Install NGINX and configure it](#61-install-nginx-and-configure-it) - - [6.2 Add the following to the location part of the server block](#62-add-the-following-to-the-location-part-of-the-server-block) - - [6.3 Check the NGINX configuration and restart it](#63-check-the-nginx-configuration-and-restart-it) - - [7. SSL Configuration with LetsEncrypt](#7-ssl-configuration-with-letsencrypt) - - [8. SSH Keys for GitHub Actions](#8-ssh-keys-for-github-actions) - - [9. GitHub Action Setup](#9-github-action-setup) - - [10. Cron Jobs](#10-cron-jobs) - - [10.1 Setting up Scripts](#101-setting-up-scripts) - - [10.1.1 Setting Permissions and Owner for correct_permissions.py](#1011-setting-permissions-and-owner-for-check_permissionssh) - - [10.1.2 Modify sudoers file to allow talawa-api to run chmod and chown without password prompt](#1012-modify-sudoers-file-to-allow-talawa-api-to-run-chmod-and-chown-without-password-prompt) - - [10.1.3 Run correct_permissions.py once to correct permissions for other scripts](#1013-run-check_permissionssh-once-to-correct-permissions-for-other-scripts) - - [10.2 Setting up Cronjobs](#102-setting-up-cronjobs) - - [10.2.1 Cron job to run correct_permissions.py](#1021-cron-job-to-run-check_permissionssh) - - [10.2.2 Cron job to run renew_certificates.py](#1022-cron-job-to-run-cert_renewsh) - - [10.2.3 Cron job to run eset_database.py](#1023-cron-job-to-run-reset_mongosh) - - [10.3 Logging for cron jobs](#103-logging-for-cron-jobs) - -## 1. Virtual Private Server (VPS) Setup +## Table Of Contents + +- [1. Virtual Private Server (VPS) Setup](#1-virtual-private-server-vps-setup) +- [2. Repository Setup](#2-repository-setup) +- [3. Docker Configuration](#3-docker-configuration) +- [4. Running the Containers](#4-running-the-containers) +- [5. Firewall Setup](#5-firewall-setup) +- [6. SSH Keys for GitHub Actions](#6-ssh-keys-for-github-actions) +- [7. GitHub Action Setup](#7-github-action-setup) +- [8. Cron Jobs](#8-cron-jobs) + - [8.1 Setting up Scripts](#81-setting-up-scripts) + - [8.1.1 Setting Permissions and Owner for correct_permissions.py](#811-setting-permissions-and-owner-for-correct_permissionspy) + - [8.1.2 Modify sudoers file to allow talawa-api to run chmod and chown without password prompt](#812-modify-sudoers-file-to-allow-talawa-api-to-run-chmod-and-chown-without-password-prompt) + - [8.1.3 Run correct_permissions.py once to correct permissions for other scripts](#813-run-correct_permissionspy-once-to-correct-permissions-for-other-scripts) + - [8.2 Setting up Cronjobs](#82-setting-up-cronjobs) + - [8.2.1 Cron job to run correct_permissions.py](#821-cron-job-to-run-correct_permissionspy) + - [8.2.3 Cron job to run reset_database.py](#823-cron-job-to-run-reset_databasepy) + - [8.3 Logging for cron jobs](#83-logging-for-cron-jobs) + +### 1. Virtual Private Server (VPS) Setup First, update your package lists and upgrade the system: @@ -44,33 +37,33 @@ sudo apt-get update && sudo apt-get upgrade Next, install curl: ```bash -sudo apt-get install curl +sudo apt-get install curl ``` Then, install Node Version Manager (nvm): ```bash -sudo curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash +sudo curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash -source ~/.bashrc +source ~/.bashrc -nvm install --lts +nvm install --lts ``` -## 2. Repository Setup +### 2. Repository Setup Create a new directory and clone the Talawa API repository: ```bash -mkdir develop -cd develop git clone https://github.com/PalisadoesFoundation/talawa-api.git . +cd talawa-api npm install +npm run setup ``` -## 3. Docker Configuration +### 3. Docker Configuration -After that, to setup docker first remove all the conflicting packages: +After that, to setup docker, first remove all the conflicting packages: ```bash for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done @@ -78,9 +71,9 @@ for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker c Before you install Docker Engine for the first time on a new host machine, you need to set up the Docker repository. Afterward, you can install and update Docker from the repository. -### 3.1 Set up docker's repository: +#### 3.1 Set up docker's repository: -#### 3.1.1 Add Docker's official GPG key: +##### 3.1.1 Add Docker's official GPG key: ```bash sudo apt-get update @@ -90,7 +83,7 @@ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyring sudo chmod a+r /etc/apt/keyrings/docker.asc ``` -#### 3.1.2 Add the repository to apt sources: +##### 3.1.2 Add the repository to apt sources: ```bash echo \ @@ -101,31 +94,30 @@ echo \ sudo apt-get update ``` -### 3.2 Install the Docker packages: +#### 3.2 Install the Docker packages: ```bash sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` -### 3.3 Allow docker to run without sudo +#### 3.3 Allow docker to run without sudo ```bash sudo groupadd docker sudo usermod -aG docker $USER ``` -- **Note : Reboot the machine to apply the changes** - -## 4. Running the Containers +### 4. Running the Containers Start the containers and import sample data: ```bash -docker-compose up -d --build -npm run import:sample-data +cd ~/talawa-api/ +docker compose -f docker-compose.dev.yaml up -d +npm run import:sample-data ``` -## 5. Firewall Setup +### 5. Firewall Setup Enable the firewall and allow SSH, HTTP, and HTTPS: @@ -137,49 +129,7 @@ sudo ufw enable sudo ufw status ``` -## 6. NGINX Installation and Configuration - -### 6.1 Install NGINX and configure it: - -```bash -sudo apt install nginx -sudo vi /etc/nginx/sites-available/default -``` - -### 6.2 Add the following to the location part of the server block: - -```bash -server_name yourdomain.com www.yourdomain.com; - -location / { - proxy_pass http://localhost:4000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; -} -``` - -### 6.3 Check the NGINX configuration and restart it: - -```bash -sudo nginx -t -sudo nginx -s reload -``` - -## 7. SSL Configuration with LetsEncrypt - -Add SSL with LetsEncrypt: - -``` -sudo add-apt-repository ppa:certbot/certbot -sudo apt-get update -sudo apt-get install python3-certbot-nginx -sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com -``` - -## 8. SSH Keys for GitHub Actions +### 6. SSH Keys for GitHub Actions For secure communication between GitHub Actions and the API VPS, you'll need to generate SSH keys and add the public key to the authorized keys on your VPS. Here's how you can do it: @@ -205,7 +155,7 @@ For secure communication between GitHub Actions and the API VPS, you'll need to cat ~/.ssh/id_ed25519 ``` -## 9. GitHub Action Setup +### 7. GitHub Action Setup To enable continuous integration with GitHub Actions, you need to set up the necessary secrets for the workflow. These secrets allow secure communication between the GitHub Actions workflow and your VPS. Here are the steps to set up the required secrets: @@ -229,9 +179,9 @@ Please replace the example values with your actual values. These secrets are crucial for the GitHub Actions workflow to connect securely to your VPS and deploy the Talawa API. -## 10. Cron Jobs +### 8. Cron Jobs -### 10.1 Setting up Scripts: +#### 8.1 Setting up Scripts: Copy the following scripts from **/home/talawa-api/develop/talawa-api/scripts/cloud-api-demo** to **/usr/local/bin/scripts**: `renew_certificates.py` @@ -240,14 +190,14 @@ Copy the following scripts from **/home/talawa-api/develop/talawa-api/scripts/cl `reset_database.py` `create_env.py` -#### 10.1.1 Setting Permissions and Owner for correct_permissions.py: +##### 8.1.1 Setting Permissions and Owner for correct_permissions.py: ```bash sudo chmod 700 /usr/local/bin/scripts/correct_permissions.py sudo chown talawa-api /usr/local/bin/scripts/correct_permissions.py ``` -#### 10.1.2 Modify sudoers file to allow talawa-api to run chmod and chown without password prompt: +##### 8.1.2 Modify sudoers file to allow talawa-api to run chmod and chown without password prompt: - Open sudoers file with sudo visudo. - Add the following line: @@ -258,7 +208,7 @@ talawa-api ALL=(ALL) NOPASSWD: /bin/chmod, /bin/chown - Save and exit the editor -#### 10.1.3 Run `correct_permissions.py` once to correct permissions for other scripts: +##### 8.1.3 Run `correct_permissions.py` once to correct permissions for other scripts: ```bash python3 correct_permissions.py --user talawa-api --files /usr/local/bin/scripts/deploy.py /usr/local/bin/scripts/reset_database.py /usr/local/bin/scripts/renew_certificates.py /usr/local/bin/scripts/create_env.py @@ -266,9 +216,9 @@ python3 correct_permissions.py --user talawa-api --files /usr/local/bin/scripts/ Executing `correct_permissions.py` once will ensure that the correct permissions are applied to the other scripts in the specified directory. -### 10.2 Setting up Cronjobs: +#### 8.2 Setting up Cronjobs: -#### 10.2.1 Cron job to run correct_permissions.py +##### 8.2.1 Cron job to run correct_permissions.py This cron job will execute correct_permissions.py every midnight, ensuring that the correct permissions are maintained for the scripts : @@ -276,15 +226,7 @@ This cron job will execute correct_permissions.py every midnight, ensuring that echo "0 0 * * * talawa-api python3 correct_permissions.py --user talawa-api --files /usr/local/bin/scripts/deploy.py /usr/local/bin/scripts/reset_database.py /usr/local/bin/scripts/renew_certificates.py /usr/local/bin/scripts/create_env.py" | sudo tee /etc/cron.d/check_permissions ``` -#### 10.2.2 Cron job to run renew_certificates.py - -This cron job will execute `renew_certificates.py` every 90 days, ensuring that the certificates are renewed in a timely manner: - -```bash -echo "0 0 * * * talawa-api python3 renew_certificates.py --config-dir ~/.certbot/config --logs-dir ~/.certbot/logs --work-dir ~/.certbot/work" | sudo tee /etc/cron.d/cert_renew -``` - -#### 10.2.3 Cron job to run reset_database.py +##### 8.2.3 Cron job to run reset_database.py This cron job will execute `reset_database.py` every 24 hours, ensuring that the MongoDB is reset on a daily basis: @@ -292,55 +234,7 @@ This cron job will execute `reset_database.py` every 24 hours, ensuring that the echo "0 * * * * talawa-api python3 reset_database.py --mongo-container develop-mongodb-1 --mongo-db talawa-api --repo-dir /home/talawa-api/develop" | sudo tee /etc/cron.d/reset_mongo ``` -#### 10.3 Logging for cron jobs - -1. **Create the logrotate configuration file:** - -```bash -sudo nano /etc/logrotate.d/talawa-api-cron -sudo mkdir -p /var/log/talawa-api/ -sudo chown talawa-api /var/log/talawa-api/ -``` - -2. **Add the following content to the file:** - -```log -/var/log/talawa-api/cron.log { - rotate 7 - daily - missingok - notifempty - compress - delaycompress - create 640 talawa-api - sharedscripts - postrotate - systemctl restart cron - endscript -} -``` - -**Explanation:** - -- `rotate 7`: Retains the last 7 rotated log files. -- `daily`: Rotates the log file daily. -- `missingok`: Ignores errors if the log file is missing. -- `notifempty`: Does not rotate the log file if it is empty. -- `compress`: Compresses rotated log files. -- `delaycompress`: Delays compression until the next rotation cycle. -- `create 640 talawa-api`: Creates new log files with the specified permissions and ownership. In this case, the owner is set to talawa-api. -- `sharedscripts`: Runs the `postrotate` script only once even if multiple log files are rotated. -- `postrotate` ... endscript: Defines the actions to be taken after log rotation, in this case, restarting the cron service. - -3. **Save and exit the text editor (Ctrl + X, then Y, then Enter in nano).** - -4. **Restart Cron Service:** - Apply the logrotate changes by restarting the cron service: - -```bash -sudo systemctl restart cron -``` - -Now, the cron job output will be logged to `/var/log/talawa-api/cron.log`, and log rotation will be managed by logrotate according to the specified configuration. Adjust the log rotation parameters in the logrotate configuration file as needed. +### 8.3 Logging for cron jobs This will set up logging for the cron jobs and manage log rotation using logrotate. + diff --git a/scripts/cloud-api-demo/create_env.py b/scripts/cloud-api-demo/create_env.py index 9c3c2e23cf..4dd571fbb7 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 98571be51c..3d31dd86ba 100644 --- a/setup.ts +++ b/setup.ts @@ -1,10 +1,8 @@ -// eslint-disable-next-line 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 { MongoClient } from "mongodb"; @@ -31,7 +29,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 +168,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 +181,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 +197,8 @@ async function askForTransactionLogPath(): Promise { ); } } - return logPath; + + return logPath as string; } //Wipes the existing data in the database @@ -219,7 +220,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 +247,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(); @@ -672,6 +673,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 +858,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 +874,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, }); @@ -771,6 +924,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 +932,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 +1016,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 +1061,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 +1137,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 +1164,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 { diff --git a/src/env.ts b/src/env.ts index d49f7d3ce4..c409cb2127 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/minioInstallationCheck.ts b/src/minioInstallationCheck.ts new file mode 100644 index 0000000000..628e480df9 --- /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 ac9d191012..fe99964a0a 100644 --- a/src/models/ActionItem.ts +++ b/src/models/ActionItem.ts @@ -4,56 +4,63 @@ import type { InterfaceUser } from "./User"; import type { InterfaceEvent } from "./Event"; import type { InterfaceActionItemCategory } from "./ActionItemCategory"; import { MILLISECONDS_IN_A_WEEK } from "../constants"; +import type { InterfaceOrganization } from "./Organization"; /** * Interface representing a database document for ActionItem in MongoDB. */ export interface InterfaceActionItem { _id: Types.ObjectId; - assigneeId: PopulatedDoc; - assignerId: PopulatedDoc; - actionItemCategoryId: PopulatedDoc; + assignee: PopulatedDoc; + assigner: PopulatedDoc; + actionItemCategory: PopulatedDoc< + InterfaceActionItemCategory & Document + > | null; preCompletionNotes: string; postCompletionNotes: string; assignmentDate: Date; dueDate: Date; completionDate: Date; isCompleted: boolean; - eventId: PopulatedDoc; - creatorId: PopulatedDoc; + allotedHours: number | null; + organization: PopulatedDoc; + event: PopulatedDoc; + creator: PopulatedDoc; createdAt: Date; updatedAt: Date; } /** * Defines the schema for the ActionItem document. - * @param assigneeId - User to whom the ActionItem is assigned. - * @param assignerId - User who assigned the ActionItem. - * @param actionItemCategoryId - ActionItemCategory to which the ActionItem belongs. + * @param assignee - User to whom the ActionItem is assigned. + * @param assigner - User who assigned the ActionItem. + * @param actionItemCategory - ActionItemCategory to which the ActionItem belongs. * @param preCompletionNotes - Notes recorded before completion. * @param postCompletionNotes - Notes recorded after completion. * @param assignmentDate - Date when the ActionItem was assigned. * @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 eventId - Optional: Event to which the ActionItem is related. - * @param creatorId - User who created the ActionItem. + * @param allotedHours - Optional: Number of hours alloted 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. * @param createdAt - Timestamp when the ActionItem was created. * @param updatedAt - Timestamp when the ActionItem was last updated. */ const actionItemSchema = new Schema( { - assigneeId: { + assignee: { type: Schema.Types.ObjectId, ref: "User", required: true, }, - assignerId: { + assigner: { type: Schema.Types.ObjectId, ref: "User", required: true, }, - actionItemCategoryId: { + actionItemCategory: { type: Schema.Types.ObjectId, ref: "ActionItemCategory", required: true, @@ -84,11 +91,19 @@ const actionItemSchema = new Schema( required: true, default: false, }, - eventId: { + allotedHours: { + type: Number, + }, + organization: { + type: Schema.Types.ObjectId, + ref: "Organization", + required: true, + }, + event: { type: Schema.Types.ObjectId, ref: "Event", }, - creatorId: { + creator: { type: Schema.Types.ObjectId, ref: "User", required: true, diff --git a/src/models/AppUserProfile.ts b/src/models/AppUserProfile.ts index ce0c07eb03..34ca5cea4f 100644 --- a/src/models/AppUserProfile.ts +++ b/src/models/AppUserProfile.ts @@ -4,6 +4,8 @@ import mongoosePaginate from "mongoose-paginate-v2"; import type { InterfaceEvent } from "./Event"; import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; +import type { InterfaceFundraisingCampaign } from "./FundraisingCampaign"; +import type { InterfaceFundraisingCampaignPledges } from "./FundraisingCampaignPledge"; export interface InterfaceAppUserProfile { _id: Types.ObjectId; @@ -13,6 +15,8 @@ export interface InterfaceAppUserProfile { createdEvents: PopulatedDoc[]; createdOrganizations: PopulatedDoc[]; eventAdmin: PopulatedDoc[]; + pledges: PopulatedDoc[]; + campaigns: PopulatedDoc[]; pluginCreationAllowed: boolean; token: string | undefined; tokenVersion: number; @@ -26,6 +30,8 @@ export interface InterfaceAppUserProfile { * @param createdEvents - Array of events created by the user. * @param createdOrganizations - Array of organizations created by the user. * @param eventAdmin - Array of events where the user is an admin. + * @param pledges - Array of pledges associated with the user. + * @param campaigns - Array of campaigns associated with the user. * @param pluginCreationAllowed - Flag indicating if user is allowed to create plugins. * @param tokenVersion - Token version for authentication. * @param isSuperAdmin - Flag indicating if the user is a super admin. @@ -68,6 +74,18 @@ const appUserSchema = new Schema( ref: "Event", }, ], + pledges: [ + { + type: Schema.Types.ObjectId, + ref: "FundraisingCampaignPledge", + }, + ], + campaigns: [ + { + type: Schema.Types.ObjectId, + ref: "FundraisingCampaign", + }, + ], pluginCreationAllowed: { type: Boolean, required: true, diff --git a/src/models/Community.ts b/src/models/Community.ts index d687748a0b..329254aae7 100644 --- a/src/models/Community.ts +++ b/src/models/Community.ts @@ -12,7 +12,7 @@ export interface InterfaceCommunity { socialMediaUrls: { facebook: string; instagram: string; - twitter: string; + X: string; linkedIn: string; gitHub: string; youTube: string; @@ -29,7 +29,7 @@ export interface InterfaceCommunity { * @param socialMediaUrls - Object containing social media URLs for the community. * @param facebook - Facebook URL. * @param instagram - Instagram URL. - * @param twitter - Twitter URL. + * @param X - X URL. * @param linkedIn - LinkedIn URL. * @param gitHub - GitHub URL. * @param youTube - YouTube URL. @@ -54,7 +54,7 @@ const communitySchema = new Schema({ instagram: { type: String, }, - twitter: { + X: { type: String, }, linkedIn: { diff --git a/src/models/Fund.ts b/src/models/Fund.ts index 6c4cc93bc6..09064a9abd 100644 --- a/src/models/Fund.ts +++ b/src/models/Fund.ts @@ -1,8 +1,8 @@ import type { Model, PopulatedDoc, Types } from "mongoose"; + import { Schema, model, models } from "mongoose"; import type { InterfaceFundraisingCampaign } from "./FundraisingCampaign"; import type { InterfaceUser } from "./User"; - /** * This is an interface representing a document for a fund in the database (MongoDB). * This interface defines the structure and types of data that a fund document will hold. @@ -24,7 +24,6 @@ export interface InterfaceFund { /** * Mongoose schema definition for a fund document. * This schema defines how the data will be stored in the MongoDB database. - * * @param organizationId - Organization ID to which the fund belongs. * @param name - Name of the fund. * @param refrenceNumber - Reference number of the fund. @@ -35,7 +34,7 @@ export interface InterfaceFund { * @param campaigns - Campaigns associated with the fund. * @param createdAt - Timestamp of when the fund document was created. * @param updatedAt - Timestamp of when the fund document was last updated. - */ + **/ const fundSchema = new Schema( { organizationId: { diff --git a/src/models/FundraisingCampaign.ts b/src/models/FundraisingCampaign.ts index 33af442b4a..82b8eff1bc 100644 --- a/src/models/FundraisingCampaign.ts +++ b/src/models/FundraisingCampaign.ts @@ -177,6 +177,7 @@ export enum CurrencyType { export interface InterfaceFundraisingCampaign { _id: Types.ObjectId; fundId: PopulatedDoc; + organizationId: Types.ObjectId; name: string; startDate: Date; endDate: Date; @@ -192,6 +193,7 @@ export interface InterfaceFundraisingCampaign { * This schema defines how the data will be stored in the MongoDB database. * * @param fundId - Reference to the parent fund. + * @param organizationId - Organization ID to which the fundraising campaign belongs. * @param name - Name of the fundraising campaign. * @param startDate - Start date of the fundraising campaign. * @param endDate - End date of the fundraising campaign. @@ -206,6 +208,11 @@ const fundraisingCampaignSchema = new Schema( ref: "Fund", required: true, }, + organizationId: { + type: Schema.Types.ObjectId, + ref: "Organization", + required: true, + }, name: { type: String, required: true, diff --git a/src/models/FundraisingCampaignPledge.ts b/src/models/FundraisingCampaignPledge.ts index ef443c575f..091872f85e 100644 --- a/src/models/FundraisingCampaignPledge.ts +++ b/src/models/FundraisingCampaignPledge.ts @@ -11,7 +11,7 @@ import { type InterfaceUser } from "./User"; */ export interface InterfaceFundraisingCampaignPledges { _id: Types.ObjectId; - campaigns: PopulatedDoc[]; + campaign: PopulatedDoc; users: PopulatedDoc[]; startDate: Date; endDate: Date; @@ -24,7 +24,7 @@ export interface InterfaceFundraisingCampaignPledges { /** * Mongoose schema for a fundraising campaign pledge. * Defines the structure of the pledge document stored in MongoDB. - * @param campaigns - The fundraising campaigns associated with the pledge. + * @param campaign - The fundraising campaign associated with the pledge. * @param users - The users who made the pledge. * @param startDate - The start date of the pledge. * @param endDate - The end date of the pledge. @@ -35,13 +35,11 @@ export interface InterfaceFundraisingCampaignPledges { */ const fundraisingCampaignPledgeSchema = new Schema( { - campaigns: [ - { - type: Schema.Types.ObjectId, - ref: "FundraisingCampaign", - required: true, - }, - ], + campaign: { + type: Schema.Types.ObjectId, + ref: "FundraisingCampaign", + required: true, + }, users: [ { type: Schema.Types.ObjectId, diff --git a/src/models/IdentifierCount.ts b/src/models/IdentifierCount.ts new file mode 100644 index 0000000000..c20d2810c4 --- /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/User.ts b/src/models/User.ts index a2c22cc7c4..3908629a92 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; @@ -56,6 +58,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. @@ -78,6 +81,12 @@ export interface InterfaceUser { */ const userSchema = new Schema( { + identifier: { + type: Number, + unique: true, + required: true, + immutable: true, + }, appUserProfileId: { type: Schema.Types.ObjectId, ref: "AppUserProfile", @@ -225,6 +234,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/resolvers/ActionItem/actionItemCategory.ts b/src/resolvers/ActionItem/actionItemCategory.ts deleted file mode 100644 index 47683a0203..0000000000 --- a/src/resolvers/ActionItem/actionItemCategory.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; -import { ActionItemCategory } from "../../models"; - -/** - * Resolver function to fetch the category of an action item. - * @param parent - The parent object containing the action item data. - * @returns The category of the action item found in the database. - */ -export const actionItemCategory: ActionItemResolvers["actionItemCategory"] = - async (parent) => { - return ActionItemCategory.findOne({ - _id: parent.actionItemCategoryId, - }).lean(); - }; diff --git a/src/resolvers/ActionItem/assignee.ts b/src/resolvers/ActionItem/assignee.ts deleted file mode 100644 index 22c4c9a560..0000000000 --- a/src/resolvers/ActionItem/assignee.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; - -/** - * Resolver function for the `assignee` field of an `ActionItem`. - * - * This function fetches the user who is assigned to a specific action item. - * - * @param parent - The parent object representing the action item. It contains information about the action item, including the ID of the user assigned to it. - * @returns A promise that resolves to the user document found in the database. This document represents the user assigned to the action item. - * - * @example - * If the action item with an ID of `123` is assigned to a user with an ID of `456`, this resolver will find the user with the ID `456` in the database and return their information. - * - * @see User - The User model used to interact with the users collection in the database. - * @see ActionItemResolvers - The type definition for the resolvers of the ActionItem fields. - */ -export const assignee: ActionItemResolvers["assignee"] = async (parent) => { - return User.findOne({ - _id: parent.assigneeId, - }).lean(); -}; diff --git a/src/resolvers/ActionItem/assigner.ts b/src/resolvers/ActionItem/assigner.ts deleted file mode 100644 index 7b763a06d4..0000000000 --- a/src/resolvers/ActionItem/assigner.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; - -/** - * Resolver function for the `assigner` field of an `ActionItem`. - * - * This function fetches the user who is the assigner of a given action item. - * It uses the `assignerId` field from the parent `ActionItem` object to find the corresponding user in the database. - * The user details are then returned in a plain JavaScript object format. - * - * @param parent - The parent `ActionItem` object. This contains the `assignerId` field, which is used to find the user. - * @returns A promise that resolves to the user object found in the database, or `null` if no user is found. - * - */ -export const assigner: ActionItemResolvers["assigner"] = async (parent) => { - return User.findOne({ - _id: parent.assignerId, - }).lean(); -}; diff --git a/src/resolvers/ActionItem/creator.ts b/src/resolvers/ActionItem/creator.ts deleted file mode 100644 index f286ba0d93..0000000000 --- a/src/resolvers/ActionItem/creator.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; -import { User } from "../../models"; - -/** - * Resolver function for the `creator` field of an `ActionItem`. - * - * This function fetches the user who is the creator of a given action item. - * It uses the `creatorId` field from the parent `ActionItem` object to find the corresponding user in the database. - * The user details are then returned in a plain JavaScript object format. - * - * @param parent - The parent `ActionItem` object. This contains the `creatorId` field, which is used to find the user. - * @returns A promise that resolves to the user object found in the database, or `null` if no user is found. - * - * @example - * ```typescript - * const actionItem = { - * creatorId: "60d0fe4f5311236168a109cb" - * }; - * const user = await creator(actionItem); - * console.log(user); - * // Output might be: { _id: "60d0fe4f5311236168a109cb", name: "Jane Doe", email: "jane.doe@example.com" } - * ``` - */ -export const creator: ActionItemResolvers["creator"] = async (parent) => { - return User.findOne({ - _id: parent.creatorId, - }).lean(); -}; diff --git a/src/resolvers/ActionItem/event.ts b/src/resolvers/ActionItem/event.ts deleted file mode 100644 index b4584d964f..0000000000 --- a/src/resolvers/ActionItem/event.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; -import { Event } from "../../models"; - -/** - * Resolver function for the `event` field of an `ActionItem`. - * - * This function retrieves the event associated with a specific action item. - * - * @param parent - The parent object representing the action item. It contains information about the action item, including the ID of the associated event. - * @returns A promise that resolves to the event document found in the database. This document represents the event associated with the action item. - * - * @example - * Here's how you might use this resolver in your GraphQL schema: - * - * ```graphql - * type ActionItem { - * event: Event - * # other fields... - * } - * ``` - * - * @example - * If the action item with an ID of `123` is associated with an event with an ID of `789`, this resolver will find the event with the ID `789` in the database and return its information. - * - * @see Event - The Event model used to interact with the events collection in the database. - * @see ActionItemResolvers - The type definition for the resolvers of the ActionItem fields. - */ -export const event: ActionItemResolvers["event"] = async (parent) => { - return Event.findOne({ - _id: parent.eventId, - }).lean(); -}; diff --git a/src/resolvers/ActionItem/index.ts b/src/resolvers/ActionItem/index.ts deleted file mode 100644 index dc5979e0d9..0000000000 --- a/src/resolvers/ActionItem/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes"; -import { assignee } from "./assignee"; -import { assigner } from "./assigner"; -import { actionItemCategory } from "./actionItemCategory"; -import { event } from "./event"; -import { creator } from "./creator"; - -export const ActionItem: ActionItemResolvers = { - assignee, - assigner, - actionItemCategory, - event, - creator, -}; diff --git a/src/resolvers/ActionItemCategory/index.ts b/src/resolvers/ActionItemCategory/index.ts index 94bdba820f..9e63b3ee1b 100644 --- a/src/resolvers/ActionItemCategory/index.ts +++ b/src/resolvers/ActionItemCategory/index.ts @@ -1,8 +1,6 @@ import type { ActionItemCategoryResolvers } from "../../types/generatedGraphQLTypes"; -import { organization } from "./organization"; import { creator } from "./creator"; export const ActionItemCategory: ActionItemCategoryResolvers = { - organization, creator, }; diff --git a/src/resolvers/ActionItemCategory/organization.ts b/src/resolvers/ActionItemCategory/organization.ts deleted file mode 100644 index 5c800a5ada..0000000000 --- a/src/resolvers/ActionItemCategory/organization.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { ActionItemCategoryResolvers } from "../../types/generatedGraphQLTypes"; -import { Organization } from "../../models"; - -/** - * Resolver function for the `organization` field of an `ActionItemCategory`. - * - * This function fetches the organization associated with a given action item category. - * It uses the `organizationId` field from the parent `ActionItemCategory` object to find the corresponding organization in the database. - * The organization details are then returned in a plain JavaScript object format. - * - * @param parent - The parent `ActionItemCategory` object. This contains the `organizationId` field, which is used to find the organization. - * @returns A promise that resolves to the organization object found in the database, or `null` if no organization is found. - * - * @example - * ```typescript - * const actionItemCategory = { - * organizationId: "60d0fe4f5311236168a109cc" - * }; - * const organization = await organization(actionItemCategory); - * console.log(organization); - * // Output might be: { _id: "60d0fe4f5311236168a109cc", name: "Tech Corp", address: "123 Tech Lane" } - * ``` - */ -export const organization: ActionItemCategoryResolvers["organization"] = async ( - parent, -) => { - return Organization.findOne({ - _id: parent.organizationId, - }).lean(); -}; diff --git a/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts b/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts index 574c0d16e9..621f765c07 100644 --- a/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts +++ b/src/resolvers/Mutation/addPledgeToFundraisingCampaign.ts @@ -107,8 +107,7 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr } // Checks whether the campaign is already added to the pledge. - const pledgeCampaignIds = pledge.campaigns.map((id) => id?.toString()); - if (pledgeCampaignIds.includes(args.campaignId)) { + if (pledge.campaign?.toString() === args.campaignId) { throw new errors.ConflictError( requestContext.translate(FUNDRAISING_CAMPAIGN_ALREADY_ADDED.MESSAGE), FUNDRAISING_CAMPAIGN_ALREADY_ADDED.CODE, @@ -122,7 +121,7 @@ export const addPledgeToFundraisingCampaign: MutationResolvers["addPledgeToFundr _id: args.pledgeId, }, { - $push: { campaigns: args.campaignId }, + campaign: args.campaignId, }, { new: true }, ); diff --git a/src/resolvers/Mutation/assignUserTag.ts b/src/resolvers/Mutation/assignUserTag.ts index 37d4628d3c..ee2186642d 100644 --- a/src/resolvers/Mutation/assignUserTag.ts +++ b/src/resolvers/Mutation/assignUserTag.ts @@ -152,10 +152,32 @@ export const assignUserTag: MutationResolvers["assignUserTag"] = async ( ); } - // Assign the tag - await TagUser.create({ - ...args.input, - }); + // 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 assigneeId = args.input.userId; + + const tagUserDocs = allAncestorTags.map((tagId) => ({ + updateOne: { + filter: { userId: assigneeId, tagId }, + update: { $setOnInsert: { userId: assigneeId, tagId } }, + upsert: true, + setDefaultsOnInsert: true, + }, + })); + + await TagUser.bulkWrite(tagUserDocs); return requestUser; }; diff --git a/src/resolvers/Mutation/createActionItem.ts b/src/resolvers/Mutation/createActionItem.ts index 8491873e63..c38357a282 100644 --- a/src/resolvers/Mutation/createActionItem.ts +++ b/src/resolvers/Mutation/createActionItem.ts @@ -217,13 +217,15 @@ export const createActionItem: MutationResolvers["createActionItem"] = async ( // Creates and returns the new action item. const createActionItem = await ActionItem.create({ - assigneeId: args.data.assigneeId, - assignerId: context.userId, - actionItemCategoryId: args.actionItemCategoryId, + assignee: args.data.assigneeId, + assigner: context.userId, + actionItemCategory: args.actionItemCategoryId, preCompletionNotes: args.data.preCompletionNotes, + allotedHours: args.data.allotedHours, dueDate: args.data.dueDate, - eventId: args.data.eventId, - creatorId: context.userId, + event: args.data.eventId, + organization: actionItemCategory.organizationId, + creator: context.userId, }); return createActionItem.toObject(); diff --git a/src/resolvers/Mutation/createActionItemCategory.ts b/src/resolvers/Mutation/createActionItemCategory.ts index 711f22449e..43168d6ac3 100644 --- a/src/resolvers/Mutation/createActionItemCategory.ts +++ b/src/resolvers/Mutation/createActionItemCategory.ts @@ -111,6 +111,7 @@ export const createActionItemCategory: MutationResolvers["createActionItemCatego // Creates new actionItemCategory. const createdActionItemCategory = await ActionItemCategory.create({ name: args.name, + isDisabled: args.isDisabled, organizationId: args.organizationId, creatorId: context.userId, }); diff --git a/src/resolvers/Mutation/createFundraisingCampaign.ts b/src/resolvers/Mutation/createFundraisingCampaign.ts index b055d69b9e..c37f3b5a44 100644 --- a/src/resolvers/Mutation/createFundraisingCampaign.ts +++ b/src/resolvers/Mutation/createFundraisingCampaign.ts @@ -125,7 +125,7 @@ export const createFundraisingCampaign: MutationResolvers["createFundraisingCamp (organizationId) => organizationId?.toString() === currentOrgId?.toString(), ); - console.log(currentUserIsOrgAdmin); + if ( !currentUserIsOrgAdmin && currentUserAppProfile.isSuperAdmin === false @@ -136,11 +136,12 @@ export const createFundraisingCampaign: MutationResolvers["createFundraisingCamp USER_NOT_AUTHORIZED_ERROR.PARAM, ); } - console.log("here"); + // Creates a fundraising campaign. const campaign = await FundraisingCampaign.create({ name: args.data.name, fundId: args.data.fundId, + organizationId: args.data.organizationId, startDate: args.data.startDate, endDate: args.data.endDate, fundingGoal: args.data.fundingGoal, diff --git a/src/resolvers/Mutation/createFundraisingCampaignPledge.ts b/src/resolvers/Mutation/createFundraisingCampaignPledge.ts index 4a71863a4b..d212b441c0 100644 --- a/src/resolvers/Mutation/createFundraisingCampaignPledge.ts +++ b/src/resolvers/Mutation/createFundraisingCampaignPledge.ts @@ -4,7 +4,7 @@ import { } from "../../constants"; import { errors, requestContext } from "../../libraries"; import type { InterfaceUser } from "../../models"; -import { FundraisingCampaign, User } from "../../models"; +import { AppUserProfile, FundraisingCampaign, User } from "../../models"; import { FundraisingCampaignPledge, type InterfaceFundraisingCampaignPledges, @@ -88,7 +88,7 @@ export const createFundraisingCampaignPledge: MutationResolvers["createFundraisi // Create a new pledge. const pledge = await FundraisingCampaignPledge.create({ - campaigns: [args.data.campaignId], + campaign: args.data.campaignId, users: args.data.userIds, startDate: startDate, endDate: endDate, @@ -96,6 +96,16 @@ export const createFundraisingCampaignPledge: MutationResolvers["createFundraisi currency: args.data.currency, }); + // Update the user with the new pledge and campaign + await AppUserProfile.updateMany( + { + userId: { $in: args.data.userIds }, + }, + { + $addToSet: { pledges: pledge._id, campaigns: args.data.campaignId }, + }, + ); + // Update the campaign with the new pledge. await FundraisingCampaign.updateOne( { diff --git a/src/resolvers/Mutation/deleteAdvertisement.ts b/src/resolvers/Mutation/deleteAdvertisement.ts index ae32f9c986..9b9140a8a9 100644 --- a/src/resolvers/Mutation/deleteAdvertisement.ts +++ b/src/resolvers/Mutation/deleteAdvertisement.ts @@ -12,20 +12,43 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Deletes an advertisement based on the provided advertisement ID. + * + * This function handles the deletion of an advertisement by first verifying + * that the current user is authorized to perform this action. It checks + * whether the user exists in the cache or database, retrieves the user's + * profile, and ensures that the user has the necessary permissions to delete + * the advertisement. If the advertisement exists and the user is authorized, + * it will be deleted, and the deleted advertisement's details will be returned. + * + * @param _parent - This is an unused parameter that represents the parent resolver in the GraphQL schema. It can be ignored. + * @param args - Contains the arguments passed to the GraphQL mutation, specifically the ID of the advertisement to be deleted. + * @param context - Provides contextual information such as the current user's ID and API root URL. This is used to find the user and validate permissions. + * + * @returns The deleted advertisement's details, including the advertisement ID and media URL, if the deletion was successful. + * + */ export const deleteAdvertisement: MutationResolvers["deleteAdvertisement"] = async (_parent, args, context) => { + // Tries to find the current user in the cache using the user's ID from the context. let currentUser: InterfaceUser | null; const userFoundInCache = await findUserInCache([context.userId]); currentUser = userFoundInCache[0]; + + // If the user is not found in the cache, tries to find them in the database. if (currentUser === null) { currentUser = await User.findOne({ _id: context.userId, }).lean(); + + // If the user is found in the database, they are cached for future requests. if (currentUser !== null) { await cacheUsers([currentUser]); } } + // If the user is still not found, throws an error indicating the user does not exist. if (currentUser === null) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -33,19 +56,27 @@ export const deleteAdvertisement: MutationResolvers["deleteAdvertisement"] = USER_NOT_FOUND_ERROR.PARAM, ); } + + // Tries to find the user's profile in the cache. let currentAppUserProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), ]); currentAppUserProfile = appUserProfileFoundInCache[0]; + + // If the profile is not found in the cache, tries to find it in the database. if (currentAppUserProfile === null) { currentAppUserProfile = await AppUserProfile.findOne({ userId: currentUser._id, }).lean(); + + // If the profile is found in the database, it is cached for future requests. if (currentAppUserProfile !== null) { await cacheAppUserProfile([currentAppUserProfile]); } } + + // If the user's profile is still not found, throws an error indicating the profile does not exist. if (!currentAppUserProfile) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -54,10 +85,12 @@ export const deleteAdvertisement: MutationResolvers["deleteAdvertisement"] = ); } + // Tries to find the advertisement by its ID. const existingAdvertisement = await Advertisement.findOne({ _id: args.id, }).lean(); + // If the advertisement is not found, throws an error indicating the advertisement does not exist. if (!existingAdvertisement) { throw new errors.NotFoundError( requestContext.translate(ADVERTISEMENT_NOT_FOUND_ERROR.MESSAGE), @@ -65,9 +98,13 @@ export const deleteAdvertisement: MutationResolvers["deleteAdvertisement"] = ADVERTISEMENT_NOT_FOUND_ERROR.PARAM, ); } + + // Checks if the user is either a super admin or an admin of the organization that owns the advertisement. const userIsOrgAdmin = currentAppUserProfile.adminFor.some( (organization) => organization === existingAdvertisement?.organizationId, ); + + // If the user is not authorized to delete the advertisement, throws an error. if (!(currentAppUserProfile.isSuperAdmin || userIsOrgAdmin)) { throw new errors.UnauthenticatedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -76,16 +113,20 @@ export const deleteAdvertisement: MutationResolvers["deleteAdvertisement"] = ); } + // Prepares the advertisement object for return by including the full media URL and converting the ID to a string. const advertisement = { ...existingAdvertisement, mediaUrl: `${context.apiRootUrl}${existingAdvertisement.mediaUrl}`, _id: existingAdvertisement._id.toString(), }; + console.log(advertisement); - // Deletes the ad. + + // Deletes the advertisement from the database. await Advertisement.deleteOne({ _id: args.id, }); - // Returns deleted ad. + + // Returns the details of the deleted advertisement. return { advertisement }; }; diff --git a/src/resolvers/Mutation/forgotPassword.ts b/src/resolvers/Mutation/forgotPassword.ts index 4bbfd81a5e..cdbe052384 100644 --- a/src/resolvers/Mutation/forgotPassword.ts +++ b/src/resolvers/Mutation/forgotPassword.ts @@ -27,8 +27,8 @@ export const forgotPassword: MutationResolvers["forgotPassword"] = async ( const { userOtp, newPassword, otpToken } = args.data; try { - await jwt.verify(otpToken, ACCESS_TOKEN_SECRET as string); - } catch (error) { + jwt.verify(otpToken, ACCESS_TOKEN_SECRET as string); + } catch { throw new Error(INVALID_OTP); } diff --git a/src/resolvers/Mutation/index.ts b/src/resolvers/Mutation/index.ts index 60924ad339..390dea99dd 100644 --- a/src/resolvers/Mutation/index.ts +++ b/src/resolvers/Mutation/index.ts @@ -69,9 +69,7 @@ import { removeDirectChat } from "./removeDirectChat"; import { removeEvent } from "./removeEvent"; import { removeEventAttendee } from "./removeEventAttendee"; import { removeEventVolunteer } from "./removeEventVolunteer"; -import { removeFund } from "./removeFund"; import { removeEventVolunteerGroup } from "./removeEventVolunteerGroup"; -import { removeFundraisingCampaign } from "./removeFundraisingCampaign"; import { removeFundraisingCampaignPledge } from "./removeFundraisingCampaingPledge"; import { removeGroupChat } from "./removeGroupChat"; import { removeMember } from "./removeMember"; @@ -199,7 +197,6 @@ export const Mutation: MutationResolvers = { removeEvent, removeEventAttendee, removeEventVolunteer, - removeFund, removeEventVolunteerGroup, removeGroupChat, removeMember, @@ -247,6 +244,5 @@ export const Mutation: MutationResolvers = { updateFundraisingCampaign, updateFundraisingCampaignPledge, createFundraisingCampaignPledge, - removeFundraisingCampaign, removeFundraisingCampaignPledge, }; diff --git a/src/resolvers/Mutation/login.ts b/src/resolvers/Mutation/login.ts index d442f0ccf1..cb825c961d 100644 --- a/src/resolvers/Mutation/login.ts +++ b/src/resolvers/Mutation/login.ts @@ -35,7 +35,7 @@ export const login: MutationResolvers["login"] = async (_parent, args) => { USER_NOT_FOUND_ERROR.PARAM, ); } - // console.log(user); + const isPasswordValid = await bcrypt.compare( args.data.password, user.password as string, @@ -58,7 +58,6 @@ export const login: MutationResolvers["login"] = async (_parent, args) => { userId: user._id.toString(), appLanguageCode: "en", tokenVersion: 0, - isSuperAdmin: false, }).lean(); if (!appUserProfile) { diff --git a/src/resolvers/Mutation/removeActionItem.ts b/src/resolvers/Mutation/removeActionItem.ts index 30879154dd..d68d8eae11 100644 --- a/src/resolvers/Mutation/removeActionItem.ts +++ b/src/resolvers/Mutation/removeActionItem.ts @@ -82,7 +82,7 @@ export const removeActionItem: MutationResolvers["removeActionItem"] = async ( const actionItem = await ActionItem.findOne({ _id: args.id, }) - .populate("actionItemCategoryId") + .populate("actionItemCategory") .lean(); // Checks if the actionItem exists @@ -96,24 +96,24 @@ export const removeActionItem: MutationResolvers["removeActionItem"] = async ( const currentUserIsOrgAdmin = currentUserAppProfile.adminFor.some( (ogranizationId) => - ogranizationId === actionItem.actionItemCategoryId.organizationId || + ogranizationId === actionItem.organization || new mongoose.Types.ObjectId(ogranizationId?.toString()).equals( - actionItem.actionItemCategoryId.organizationId, + actionItem.organization, ), ); let currentUserIsEventAdmin = false; - if (actionItem.eventId) { + if (actionItem.event) { let currEvent: InterfaceEvent | null; - const eventFoundInCache = await findEventsInCache([actionItem.eventId]); + const eventFoundInCache = await findEventsInCache([actionItem.event]); currEvent = eventFoundInCache[0]; if (eventFoundInCache[0] === null) { currEvent = await Event.findOne({ - _id: actionItem.eventId, + _id: actionItem.event, }).lean(); if (currEvent !== null) { diff --git a/src/resolvers/Mutation/removeEventAttendee.ts b/src/resolvers/Mutation/removeEventAttendee.ts index fcd414355d..98259e7f2a 100644 --- a/src/resolvers/Mutation/removeEventAttendee.ts +++ b/src/resolvers/Mutation/removeEventAttendee.ts @@ -19,20 +19,42 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache"; import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile"; +/** + * Removes a user from the list of attendees for a specific event. + * + * This function manages the removal of an event attendee by first verifying + * the current user's authorization and the existence of the event. It checks + * if the user making the request is either a super admin or an admin of the event, + * and if the user to be removed is indeed registered as an attendee for the event. + * If all checks pass, the user is removed from the event's attendee list. + * + * @param _parent - This is an unused parameter representing the parent resolver in the GraphQL schema. It can be ignored. + * @param args - Contains the arguments passed to the GraphQL mutation, specifically the event ID and user ID of the attendee to be removed. + * @param context - Provides contextual information, including the current user's ID. This is used to authenticate and authorize the request. + * + * @returns The details of the removed user if the removal was successful. + * + */ export const removeEventAttendee: MutationResolvers["removeEventAttendee"] = async (_parent, args, context) => { + // Tries to find the current user in the cache using the user's ID from the context. let currentUser: InterfaceUser | null; const userFoundInCache = await findUserInCache([context.userId]); currentUser = userFoundInCache[0]; + + // If the user is not found in the cache, tries to find them in the database. if (currentUser === null) { currentUser = await User.findOne({ _id: context.userId, }).lean(); + + // If the user is found in the database, they are cached for future requests. if (currentUser !== null) { await cacheUsers([currentUser]); } } + // If the user is still not found, throws an error indicating the user does not exist. if (currentUser === null) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -40,19 +62,27 @@ export const removeEventAttendee: MutationResolvers["removeEventAttendee"] = USER_NOT_FOUND_ERROR.PARAM, ); } + + // Tries to find the current user's app profile in the cache. let currentUserAppProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), ]); currentUserAppProfile = appUserProfileFoundInCache[0]; + + // If the app profile is not found in the cache, tries to find it in the database. if (currentUserAppProfile === null) { currentUserAppProfile = await AppUserProfile.findOne({ userId: currentUser._id, }).lean(); + + // If the profile is found in the database, it is cached for future requests. if (currentUserAppProfile !== null) { await cacheAppUserProfile([currentUserAppProfile]); } } + + // If the user's app profile is not found, throws an error indicating the user is unauthorized. if (!currentUserAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -60,22 +90,25 @@ export const removeEventAttendee: MutationResolvers["removeEventAttendee"] = USER_NOT_AUTHORIZED_ERROR.PARAM, ); } - let currentEvent: InterfaceEvent | null; + // Tries to find the event in the cache. + let currentEvent: InterfaceEvent | null; const eventFoundInCache = await findEventsInCache([args.data.eventId]); - currentEvent = eventFoundInCache[0]; + // If the event is not found in the cache, tries to find it in the database. if (eventFoundInCache[0] === null) { currentEvent = await Event.findOne({ _id: args.data.eventId, }).lean(); + // If the event is found in the database, it is cached for future requests. if (currentEvent !== null) { await cacheEvents([currentEvent]); } } + // If the event is not found, throws an error indicating the event does not exist. if (currentEvent === null) { throw new errors.NotFoundError( requestContext.translate(EVENT_NOT_FOUND_ERROR.MESSAGE), @@ -84,10 +117,12 @@ export const removeEventAttendee: MutationResolvers["removeEventAttendee"] = ); } + // Checks if the current user is an admin for the event or a super admin. const isUserEventAdmin = currentEvent.admins.some( (admin) => admin.toString() === context.userId.toString(), ); + // If the user is not an event admin or a super admin, throws an error indicating they are unauthorized. if (!isUserEventAdmin && currentUserAppProfile.isSuperAdmin === false) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -96,10 +131,12 @@ export const removeEventAttendee: MutationResolvers["removeEventAttendee"] = ); } + // Tries to find the user who is to be removed as an attendee. const requestUser = await User.findOne({ _id: args.data.userId, }).lean(); + // If the user to be removed is not found, throws an error indicating the user does not exist. if (requestUser === null) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -108,10 +145,12 @@ export const removeEventAttendee: MutationResolvers["removeEventAttendee"] = ); } + // Checks if the user is already an attendee of the event. const userAlreadyAttendee = await EventAttendee.exists({ ...args.data, }); + // If the user is not registered as an attendee, throws an error indicating the conflict. if (!userAlreadyAttendee) { throw new errors.ConflictError( requestContext.translate(USER_NOT_REGISTERED_FOR_EVENT.MESSAGE), @@ -120,7 +159,9 @@ export const removeEventAttendee: MutationResolvers["removeEventAttendee"] = ); } + // Removes the user from the list of attendees. await EventAttendee.deleteOne({ ...args.data }); + // Returns the details of the removed user. return requestUser; }; diff --git a/src/resolvers/Mutation/removeFund.ts b/src/resolvers/Mutation/removeFund.ts deleted file mode 100644 index c7102948e3..0000000000 --- a/src/resolvers/Mutation/removeFund.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Types } from "mongoose"; -import { - FUND_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import { - AppUserProfile, - Fund, - User, - type InterfaceAppUserProfile, - type InterfaceFund, - type InterfaceUser, -} from "../../models"; - -import { FundraisingCampaign } from "../../models/FundraisingCampaign"; -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 to remove fund . - * @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 fund exists. - * 3. If the user is authorized. - * 4. If the user is admin of the organization. - * @returns Deleted fund. - */ - -export const removeFund: MutationResolvers["removeFund"] = async ( - parent, - 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]); - } - } - - // Checks whether 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, - ); - } - - const fund = await Fund.findOne({ - _id: args.id, - }).lean(); - - // Checks whether fund exists. - if (!fund) { - throw new errors.NotFoundError( - requestContext.translate(FUND_NOT_FOUND_ERROR.MESSAGE), - FUND_NOT_FOUND_ERROR.CODE, - FUND_NOT_FOUND_ERROR.PARAM, - ); - } - const currentOrg = await Fund.findById(fund._id) - .select("organizationId") - .lean(); - - const currentOrgId = currentOrg?.organizationId?.toString(); - - //checks whether the user is admin of organization or not - const currentUserIsOrgAdmin = currentUserAppProfile.adminFor.some( - (organizationId) => - new Types.ObjectId(organizationId?.toString()).equals(currentOrgId), - ); - - const currentUserIsSuperAdmin = currentUserAppProfile.isSuperAdmin; - - // checks if the user is either super admin or admin of the organization - if (!(currentUserIsOrgAdmin || currentUserIsSuperAdmin)) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - - //deletes all the campaigns associated with the fund - for (const campaignId of fund.campaigns) { - await FundraisingCampaign.findByIdAndDelete({ - _id: campaignId, - }); - } - - //deletes the fund - await Fund.deleteOne({ - _id: args.id, - }); - - //returns the deleted fund - return fund as InterfaceFund; -}; diff --git a/src/resolvers/Mutation/removeFundraisingCampaign.ts b/src/resolvers/Mutation/removeFundraisingCampaign.ts deleted file mode 100644 index 7bf5a6083a..0000000000 --- a/src/resolvers/Mutation/removeFundraisingCampaign.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { Types } from "mongoose"; -import { - FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR, - FUND_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../constants"; -import { errors, requestContext } from "../../libraries"; -import { - AppUserProfile, - Fund, - FundraisingCampaign, - User, - type InterfaceAppUserProfile, - type InterfaceFundraisingCampaign, - type InterfaceUser, -} 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 to remove fundraising campaign . - * @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 fundraising campaign exists. - * 3. If the user is authorized. - * 4. If the user is admin of the organization. - * @returns Deleted fundraising campaign. - */ - -export const removeFundraisingCampaign: MutationResolvers["removeFundraisingCampaign"] = - async (_parent, 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]); - } - } - - // Checks whether 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, - ); - } - - const campaign = await FundraisingCampaign.findOne({ - _id: args.id, - }).lean(); - - // Checks whether fundraising campaign exists. - if (!campaign) { - throw new errors.NotFoundError( - requestContext.translate(FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.MESSAGE), - FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.CODE, - FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.PARAM, - ); - } - const fund = await Fund.findOne({ - _id: campaign.fundId?.toString(), - }).lean(); - - // Checks whether parent fund exists. - if (!fund) { - throw new errors.NotFoundError( - requestContext.translate(FUND_NOT_FOUND_ERROR.MESSAGE), - FUND_NOT_FOUND_ERROR.CODE, - FUND_NOT_FOUND_ERROR.PARAM, - ); - } - const currentOrg = await Fund.findById(fund._id) - .select("organizationId") - .lean(); - - const currentOrgId = currentOrg?.organizationId?.toString(); - - const currentUserIsOrgAdmin = currentUserAppProfile.adminFor.some( - (organizationId) => - new Types.ObjectId(organizationId?.toString()).equals(currentOrgId), - ); - - // Checks whether the user is admin of the organization or not. - if (!(currentUserIsOrgAdmin || currentUserAppProfile.isSuperAdmin)) { - throw new errors.UnauthorizedError( - requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), - USER_NOT_AUTHORIZED_ERROR.CODE, - USER_NOT_AUTHORIZED_ERROR.PARAM, - ); - } - // Deletes the fundraising campaign. - await FundraisingCampaign.deleteOne({ - _id: args.id, - }); - - // Removes the campaign from the fund. - await Fund.updateOne( - { - _id: fund._id, - }, - { - $pull: { - campaigns: args.id, - }, - }, - ); - - // Returns the deleted fundraising campaign. - return campaign as InterfaceFundraisingCampaign; - }; diff --git a/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts b/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts index 9419fb9557..32bcb9ca36 100644 --- a/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts +++ b/src/resolvers/Mutation/removeFundraisingCampaingPledge.ts @@ -4,7 +4,7 @@ import { } from "../../constants"; import { errors, requestContext } from "../../libraries"; import type { InterfaceUser } from "../../models"; -import { FundraisingCampaign, User } from "../../models"; +import { AppUserProfile, FundraisingCampaign, User } from "../../models"; import { FundraisingCampaignPledge, type InterfaceFundraisingCampaignPledges, @@ -66,13 +66,37 @@ export const removeFundraisingCampaignPledge: MutationResolvers["removeFundraisi ); } - // Remove the pledge from the campaign. - for (const campaignId of pledge.campaigns) { - await FundraisingCampaign.updateOne( - { _id: campaignId?.toString() }, + // Update AppUserProfile for every pledger + for (const userId of pledge.users) { + const updatedUserProfile = await AppUserProfile.findOneAndUpdate( + { userId }, { $pull: { pledges: args.id } }, + { new: true }, + ).populate("pledges"); + + // Remove campaign from appUserProfile if there is no pledge left for that campaign. + const pledges = + updatedUserProfile?.pledges as InterfaceFundraisingCampaignPledges[]; + + const campaignId = pledge.campaign?.toString(); + const otherPledges = pledges.filter( + (pledge) => pledge.campaign?.toString() === campaignId, ); + + if (otherPledges.length === 0) { + await AppUserProfile.updateOne( + { userId }, + { $pull: { campaigns: campaignId } }, + ); + } } + + // Remove the pledge from the campaign. + await FundraisingCampaign.updateOne( + { _id: pledge.campaign?.toString() }, + { $pull: { pledges: args.id } }, + ); + // Remove the pledge. await FundraisingCampaignPledge.deleteOne({ _id: args.id, diff --git a/src/resolvers/Mutation/removeOrganization.ts b/src/resolvers/Mutation/removeOrganization.ts index fc2def62ca..cbe4e298cc 100644 --- a/src/resolvers/Mutation/removeOrganization.ts +++ b/src/resolvers/Mutation/removeOrganization.ts @@ -185,7 +185,7 @@ export const removeOrganization: MutationResolvers["removeOrganization"] = // Remove all ActionItem documents whose actionItemCategory is in the actionItemCategories array await ActionItem.deleteMany({ - actionItemCategoryId: { $in: actionItemCategoriesIds }, + actionItemCategory: { $in: actionItemCategoriesIds }, }); //Remove all the funds specific to organization await Fund.deleteMany({ diff --git a/src/resolvers/Mutation/removeSampleOrganization.ts b/src/resolvers/Mutation/removeSampleOrganization.ts index 570f20ab4f..34421cb8a7 100644 --- a/src/resolvers/Mutation/removeSampleOrganization.ts +++ b/src/resolvers/Mutation/removeSampleOrganization.ts @@ -14,20 +14,40 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { removeSampleOrganization as removeSampleOrgUtil } from "../../utilities/removeSampleOrganizationUtil"; +/** + * Removes a sample organization from the system. + * + * This function allows the deletion of a sample organization by checking the current user's authorization and the existence of the organization. + * The function first verifies whether the user making the request is authorized by checking if they are either a super admin or an admin of the organization. + * If the user is authorized and the organization exists, the organization is removed from the system. + * + * @param _parent - This is an unused parameter representing the parent resolver in the GraphQL schema. It can be ignored. + * @param _args - The arguments passed to the GraphQL mutation, which are not used in this function. + * @param _context - Provides contextual information, including the current user's ID. This is used to authenticate and authorize the request. + * + * @returns A boolean value indicating whether the operation was successful. + * + */ export const removeSampleOrganization: MutationResolvers["removeSampleOrganization"] = async (_parent, _args, _context) => { + // Tries to find the current user in the cache using the user's ID from the context. let currentUser: InterfaceUser | null; const userFoundInCache = await findUserInCache([_context.userId]); currentUser = userFoundInCache[0]; + + // If the user is not found in the cache, tries to find them in the database. if (currentUser === null) { currentUser = await User.findOne({ _id: _context.userId, }).lean(); + + // If the user is found in the database, they are cached for future requests. if (currentUser !== null) { await cacheUsers([currentUser]); } } + // If the user is still not found, throws an error indicating the user does not exist. if (!currentUser) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -35,19 +55,27 @@ export const removeSampleOrganization: MutationResolvers["removeSampleOrganizati USER_NOT_FOUND_ERROR.PARAM, ); } + + // Tries to find the current user's app profile in the cache. let currentUserAppProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), ]); currentUserAppProfile = appUserProfileFoundInCache[0]; + + // If the app profile is not found in the cache, tries to find it in the database. if (currentUserAppProfile === null) { currentUserAppProfile = await AppUserProfile.findOne({ userId: currentUser._id, }).lean(); + + // If the profile is found in the database, it is cached for future requests. if (currentUserAppProfile !== null) { await cacheAppUserProfile([currentUserAppProfile]); } } + + // If the user's app profile is not found, throws an error indicating the user is unauthorized. if (!currentUserAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -56,10 +84,12 @@ export const removeSampleOrganization: MutationResolvers["removeSampleOrganizati ); } + // Tries to find the existing organization in the sample data. const existingOrganization = await SampleData.findOne({ collectionName: "Organization", }); + // If the organization is not found, throws an error indicating the organization does not exist. if (!existingOrganization) { throw new errors.NotFoundError( requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), @@ -68,6 +98,7 @@ export const removeSampleOrganization: MutationResolvers["removeSampleOrganizati ); } + // Checks if the current user is an admin for the organization or a super admin. const currentUserOrgAdmin = currentUserAppProfile.adminFor.some( (org) => org && @@ -76,6 +107,7 @@ export const removeSampleOrganization: MutationResolvers["removeSampleOrganizati ), ); + // If the user is not an organization admin or a super admin, throws an error indicating they are unauthorized. if (!currentUserAppProfile.isSuperAdmin && !currentUserOrgAdmin) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -84,6 +116,9 @@ export const removeSampleOrganization: MutationResolvers["removeSampleOrganizati ); } + // Calls the utility function to remove the sample organization. await removeSampleOrgUtil(); + + // Returns true if the organization was successfully removed. return true; }; diff --git a/src/resolvers/Mutation/removeUserCustomData.ts b/src/resolvers/Mutation/removeUserCustomData.ts index d90872ceb8..c17328416c 100644 --- a/src/resolvers/Mutation/removeUserCustomData.ts +++ b/src/resolvers/Mutation/removeUserCustomData.ts @@ -15,22 +15,40 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Removes custom data associated with the current user within a specified organization. + * + * This function allows an authorized user, such as an organization admin or super admin, to remove custom data associated with the user within a specific organization. The function first verifies the user's identity and authorization, then proceeds to delete the custom data if it exists. + * + * @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 `organizationId` for which the custom data should be removed. + * @param context - Provides contextual information, including the current user's ID. This is used to authenticate and authorize the request. + * + * @returns The removed custom data object if the operation was successful. + * + */ export const removeUserCustomData: MutationResolvers["removeUserCustomData"] = async (_parent, args, context) => { const { organizationId } = args; + // Tries to find the current user in the cache using the user's ID from the context. let currentUser: InterfaceUser | null; const userFoundInCache = await findUserInCache([context.userId]); currentUser = userFoundInCache[0]; + + // If the user is not found in the cache, tries to find them in the database. if (currentUser === null) { currentUser = await User.findOne({ _id: context.userId, }).lean(); + + // If the user is found in the database, they are cached for future requests. if (currentUser !== null) { await cacheUsers([currentUser]); } } + // If the user is still not found, throws an error indicating the user does not exist. if (!currentUser) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -38,19 +56,27 @@ export const removeUserCustomData: MutationResolvers["removeUserCustomData"] = USER_NOT_FOUND_ERROR.PARAM, ); } + + // Tries to find the current user's app profile in the cache. let currentUserAppProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), ]); currentUserAppProfile = appUserProfileFoundInCache[0]; + + // If the app profile is not found in the cache, tries to find it in the database. if (currentUserAppProfile === null) { currentUserAppProfile = await AppUserProfile.findOne({ userId: currentUser._id, }).lean(); + + // If the profile is found in the database, it is cached for future requests. if (currentUserAppProfile !== null) { await cacheAppUserProfile([currentUserAppProfile]); } } + + // If the user's app profile is not found, throws an error indicating the user is unauthorized. if (!currentUserAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -58,10 +84,13 @@ export const removeUserCustomData: MutationResolvers["removeUserCustomData"] = USER_NOT_AUTHORIZED_ERROR.PARAM, ); } + + // Tries to find the specified organization in the database. const organization = await Organization.findOne({ _id: organizationId, }).lean(); + // If the organization is not found, throws an error indicating the organization does not exist. if (!organization) { throw new errors.NotFoundError( requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), @@ -70,11 +99,13 @@ export const removeUserCustomData: MutationResolvers["removeUserCustomData"] = ); } + // Checks if the current user is an admin for the organization or a super admin. const currentUserIsOrganizationAdmin = currentUserAppProfile.adminFor.some( (orgId) => orgId && new Types.ObjectId(orgId?.toString()).equals(organization._id), ); + // If the user is not an organization admin or a super admin, throws an error indicating they are unauthorized. if ( !(currentUserIsOrganizationAdmin || currentUserAppProfile.isSuperAdmin) ) { @@ -85,11 +116,13 @@ export const removeUserCustomData: MutationResolvers["removeUserCustomData"] = ); } + // Tries to find and delete the user's custom data associated with the specified organization. const userCustomData = await UserCustomData.findOneAndDelete({ userId: context.userId, organizationId, }).lean(); + // If the custom data is not found, throws an error indicating it does not exist. if (!userCustomData) { throw new errors.NotFoundError( requestContext.translate(CUSTOM_DATA_NOT_FOUND.MESSAGE), @@ -98,5 +131,6 @@ export const removeUserCustomData: MutationResolvers["removeUserCustomData"] = ); } + // Returns the removed custom data. return userCustomData; }; diff --git a/src/resolvers/Mutation/removeUserTag.ts b/src/resolvers/Mutation/removeUserTag.ts index e95ed3cb93..ab2a0645c8 100644 --- a/src/resolvers/Mutation/removeUserTag.ts +++ b/src/resolvers/Mutation/removeUserTag.ts @@ -17,6 +17,28 @@ import { findAppUserProfileCache } from "../../services/AppUserProfileCache/find import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Removes a user tag from an organization. + * + * This function removes a specific tag associated with a user in an organization. + * It checks whether the user has the proper authorization to delete the tag. + * It also handles cases where the user or the tag is not found in the system. + * + * The function performs the following steps: + * 1. Attempts to find the user in the cache or database. + * 2. Verifies if the user exists. + * 3. Attempts to find the user's profile in the cache or database. + * 4. Checks if the user has the necessary permissions to delete the tag. + * 5. Fetches the tag that needs to be deleted. + * 6. Retrieves all child tags (including the parent tag) related to the organization. + * 7. Deletes all related tags from the organization and user tag entries. + * + * @param _parent - This parameter is not used in this resolver function. + * @param args - The arguments provided by the GraphQL query, specifically containing the ID of the tag to be removed. + * @param context - The context of the request, containing information about the currently authenticated user. + * + * @returns The tag that was deleted. + */ export const removeUserTag: MutationResolvers["removeUserTag"] = async ( _parent, diff --git a/src/resolvers/Mutation/togglePostPin.ts b/src/resolvers/Mutation/togglePostPin.ts index 635d055fd8..c69434b0a7 100644 --- a/src/resolvers/Mutation/togglePostPin.ts +++ b/src/resolvers/Mutation/togglePostPin.ts @@ -25,12 +25,24 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Toggles the pinning status of a post within an organization. + * + * This function allows an authorized user, such as an organization admin or super admin, to pin or unpin a post within an organization. If the post is already pinned, it will be unpinned, and if it is not pinned, it will be pinned. The function ensures that only authorized users can perform this action and that the title provided for pinning meets validation requirements. + * + * @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 optionally the `title` to be used if pinning the post. + * @param context - Provides contextual information, including the current user's ID. This is used to authenticate and authorize the request. + * + * @returns The updated post object after the pinning status has been toggled. + * + */ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( _parent, args, context, ) => { - // Get the current user + // Get the current user from the cache or database let currentUser: InterfaceUser | null; const userFoundInCache = await findUserInCache([context.userId]); currentUser = userFoundInCache[0]; @@ -43,7 +55,7 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( } } - // Check if the user requesting the action exits + // Check if the user exists if (!currentUser) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -51,6 +63,8 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( USER_NOT_FOUND_ERROR.PARAM, ); } + + // Get the current user's app profile from the cache or database let currentUserAppProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), @@ -64,6 +78,8 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( await cacheAppUserProfile([currentUserAppProfile]); } } + + // Check if the user's app profile exists if (!currentUserAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -71,11 +87,10 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( USER_NOT_AUTHORIZED_ERROR.PARAM, ); } - // Check if the post object exists - let post: InterfacePost | null; + // Get the post from the cache or database + let post: InterfacePost | null; const postFoundInCache = await findPostsInCache([args.id]); - post = postFoundInCache[0]; if (postFoundInCache[0] === null) { @@ -87,6 +102,7 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( } } + // Check if the post exists if (!post) { throw new errors.NotFoundError( requestContext.translate(POST_NOT_FOUND_ERROR.MESSAGE), @@ -95,7 +111,7 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( ); } - // Check if the current user is authorized to perform the operation + // Check if the user is authorized to pin or unpin the post const currentUserIsOrganizationAdmin = currentUserAppProfile.adminFor.some( (organizationId) => organizationId && @@ -112,9 +128,8 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( ); } - // Toggle the post's status for the organization + // Toggle the pinning status of the post within the organization let organization; - const organizationFoundInCache = await findOrganizationsInCache([ post.organization, ]); @@ -129,11 +144,13 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( await cacheOrganizations([organization]); } } + const currentPostIsPinned = organization?.pinnedPosts.some((postID) => new mongoose.Types.ObjectId(postID.toString()).equals(args.id), ); if (currentPostIsPinned) { + // Unpin the post if it is currently pinned const updatedOrganization = await Organization.findOneAndUpdate( { _id: post.organization, @@ -170,6 +187,7 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( return updatedPost as InterfacePost; } else { + // Pin the post if it is not currently pinned if (!args.title) { throw new errors.InputValidationError( requestContext.translate(PLEASE_PROVIDE_TITLE.MESSAGE), @@ -177,6 +195,7 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( ); } + // Validate the title length if provided if (args?.title) { const validationResultTitle = isValidString(args?.title, 256); if (!validationResultTitle.isLessThanMaxLength) { @@ -206,6 +225,7 @@ export const togglePostPin: MutationResolvers["togglePostPin"] = async ( if (updatedOrganization !== null) { await cacheOrganizations([updatedOrganization]); } + const updatedPost = await Post.findOneAndUpdate( { _id: args.id, diff --git a/src/resolvers/Mutation/unassignUserTag.ts b/src/resolvers/Mutation/unassignUserTag.ts index 9828701d9e..cd19f6d891 100644 --- a/src/resolvers/Mutation/unassignUserTag.ts +++ b/src/resolvers/Mutation/unassignUserTag.ts @@ -19,6 +19,31 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Unassigns a tag from a user in an organization. + * + * This function removes a specific tag from a user in an organization. + * It checks whether the current user has the necessary permissions to unassign the tag and + * verifies if the tag and the user exist in the system. If the tag is not currently assigned + * to the user, an error is thrown. + * + * The function performs the following steps: + * 1. Attempts to find the current user in the cache or database. + * 2. Verifies if the current user exists. + * 3. Attempts to find the current user's profile in the cache or database. + * 4. Checks if the current user has the necessary permissions to unassign the tag. + * 5. Fetches the tag that needs to be unassigned. + * 6. Checks if the user to whom the tag is assigned exists. + * 7. Ensures that the tag is actually assigned to the user. + * 8. Removes the tag assignment from the user. + * + * @param _parent - This parameter is not used in this resolver function. + * @param args - The arguments provided by the GraphQL query, specifically containing the user ID and tag ID to unassign. + * @param context - The context of the request, containing information about the currently authenticated user. + * + * @returns The user from whom the tag was unassigned. + */ + export const unassignUserTag: MutationResolvers["unassignUserTag"] = async ( _parent, args, @@ -120,9 +145,36 @@ export const unassignUserTag: MutationResolvers["unassignUserTag"] = async ( ); } + // Get all the child tags of the current tag (including itself) + // on the OrganizationTagUser model + // The following implementation makes number of queries = max depth of nesting in the tag provided + let allTagIds: string[] = []; + let currentParents = [tag._id.toString()]; + + while (currentParents.length) { + allTagIds = allTagIds.concat(currentParents); + const foundTags = await OrganizationTagUser.find( + { + organizationId: tag.organizationId, + parentTagId: { + $in: currentParents, + }, + }, + { + _id: 1, + }, + ); + currentParents = foundTags + .map((tag) => tag._id.toString()) + .filter((id: string | null) => id); + } + // Unassign the tag - await TagUser.deleteOne({ - ...args.input, + await TagUser.deleteMany({ + tagId: { + $in: allTagIds, + }, + userId: args.input.userId, }); return requestUser; diff --git a/src/resolvers/Mutation/updateActionItem.ts b/src/resolvers/Mutation/updateActionItem.ts index b00f741b78..265a40104c 100644 --- a/src/resolvers/Mutation/updateActionItem.ts +++ b/src/resolvers/Mutation/updateActionItem.ts @@ -40,6 +40,7 @@ type UpdateActionItemInputType = { preCompletionNotes: string; postCompletionNotes: string; dueDate: Date; + allotedHours: number; completionDate: Date; isCompleted: boolean; }; @@ -93,7 +94,7 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( const actionItem = await ActionItem.findOne({ _id: args.id, }) - .populate("actionItemCategoryId") + .populate("actionItemCategory") .lean(); // Checks if the actionItem exists @@ -109,7 +110,7 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( if (args.data.assigneeId) { sameAssignedUser = new mongoose.Types.ObjectId( - actionItem.assigneeId.toString(), + actionItem.assignee.toString(), ).equals(args.data.assigneeId); if (!sameAssignedUser) { @@ -127,7 +128,7 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( } let userIsOrganizationMember = false; - const currorganizationId = actionItem.actionItemCategoryId.organizationId; + const currorganizationId = actionItem.actionItemCategory.organizationId; userIsOrganizationMember = newAssignedUser.joinedOrganizations.some( (organizationId) => organizationId === currorganizationId || @@ -149,24 +150,24 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( const currentUserIsOrgAdmin = currentUserAppProfile.adminFor.some( (ogranizationId) => - ogranizationId === actionItem.actionItemCategoryId.organizationId || + ogranizationId === actionItem.organization || new mongoose.Types.ObjectId(ogranizationId?.toString()).equals( - actionItem.actionItemCategoryId.organizationId, + actionItem.organization, ), ); let currentUserIsEventAdmin = false; - if (actionItem.eventId) { + if (actionItem.event) { let currEvent: InterfaceEvent | null; - const eventFoundInCache = await findEventsInCache([actionItem.eventId]); + const eventFoundInCache = await findEventsInCache([actionItem.event]); currEvent = eventFoundInCache[0]; if (eventFoundInCache[0] === null) { currEvent = await Event.findOne({ - _id: actionItem.eventId, + _id: actionItem.event, }).lean(); if (currEvent !== null) { @@ -209,7 +210,7 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( : new Date(); const updatedAssigner = sameAssignedUser - ? actionItem.assignerId + ? actionItem.assigner : context.userId; const updatedActionItem = await ActionItem.findOneAndUpdate( @@ -218,8 +219,9 @@ export const updateActionItem: MutationResolvers["updateActionItem"] = async ( }, { ...(args.data as UpdateActionItemInputType), + assignee: args.data.assigneeId || actionItem.assignee, assignmentDate: updatedAssignmentDate, - assignerId: updatedAssigner, + assigner: updatedAssigner, }, { new: true, diff --git a/src/resolvers/Mutation/updateAdvertisement.ts b/src/resolvers/Mutation/updateAdvertisement.ts index e26880e3e4..fbca16591c 100644 --- a/src/resolvers/Mutation/updateAdvertisement.ts +++ b/src/resolvers/Mutation/updateAdvertisement.ts @@ -22,12 +22,23 @@ import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage"; import { uploadEncodedVideo } from "../../utilities/encodedVideoStorage/uploadEncodedVideo"; - +/** + * Updates an advertisement with new details, including handling media file uploads and validating input fields. + * + * This function updates an existing advertisement based on the provided input. It checks for required fields, validates dates, handles media file uploads, and performs authorization checks to ensure that the current user has the right to update the advertisement. The function returns the updated advertisement 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 advertisement's `_id` and other fields to update. This may include `startDate`, `endDate`, and `mediaFile`. + * @param context - Provides contextual information, including the current user's ID. This is used to authenticate and authorize the request. + * + * @returns An object containing the updated advertisement with all its fields. + * + */ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = async (_parent, args, context) => { const { _id, ...otherFields } = args.input; - //If there is no input + // Check if input is provided if (Object.keys(otherFields).length === 0) { throw new errors.InputValidationError( requestContext.translate(INPUT_NOT_FOUND_ERROR.MESSAGE), @@ -36,7 +47,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = ); } - // Check for unintended null values in permitted fields, if all fields are permitted + // Check for unintended null values in permitted fields for (const fieldValue of Object.values(args.input)) { if ( fieldValue === null || @@ -50,6 +61,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = } } + // Retrieve the current user from cache or database let currentUser: InterfaceUser | null; const userFoundInCache = await findUserInCache([context.userId]); currentUser = userFoundInCache[0]; @@ -62,6 +74,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = } } + // Check if the current user exists if (!currentUser) { throw new errors.NotFoundError( requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), @@ -70,6 +83,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = ); } + // Retrieve the current user's app profile from cache or database let currentUserAppProfile: InterfaceAppUserProfile | null; const appUserProfileFoundInCache = await findAppUserProfileCache([ currentUser.appUserProfileId?.toString(), @@ -83,6 +97,8 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = await cacheAppUserProfile([currentUserAppProfile]); } } + + // Check if the user's app profile exists if (!currentUserAppProfile) { throw new errors.UnauthorizedError( requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), @@ -90,6 +106,8 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = USER_NOT_AUTHORIZED_ERROR.PARAM, ); } + + // Retrieve the advertisement from the database const advertisement = await Advertisement.findOne({ _id: _id, }); @@ -101,6 +119,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = ); } + // Check if the user is authorized to update the advertisement const userIsOrganizationAdmin = currentUserAppProfile.adminFor.some( (organisation) => organisation === advertisement.organizationId || @@ -118,7 +137,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = const { startDate, endDate } = args.input; - //If startDate is less than or equal to current date + // Validate startDate and endDate if ( startDate && new Date(startDate) <= new Date(new Date().toDateString()) @@ -141,6 +160,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = let uploadMediaFile = null; + // Handle media file upload if (args.input.mediaFile) { const dataUrlPrefix = "data:"; if (args.input.mediaFile.startsWith(dataUrlPrefix + "image/")) { @@ -152,10 +172,12 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = } } + // Prepare fields to update const fieldsToUpdate = args.input.mediaFile ? { ...args.input, mediaUrl: uploadMediaFile } : { ...args.input }; + // Update the advertisement in the database const updatedAdvertisement = await Advertisement.findOneAndUpdate( { _id: _id, @@ -168,6 +190,7 @@ export const updateAdvertisement: MutationResolvers["updateAdvertisement"] = }, ).lean(); + // Prepare and return the updated advertisement payload const updatedAdvertisementPayload = { _id: updatedAdvertisement?._id?.toString(), // Ensure _id is converted to String as per GraphQL schema name: updatedAdvertisement?.name, diff --git a/src/resolvers/Mutation/updateCommunity.ts b/src/resolvers/Mutation/updateCommunity.ts index bc9107cef0..dfc672eeda 100644 --- a/src/resolvers/Mutation/updateCommunity.ts +++ b/src/resolvers/Mutation/updateCommunity.ts @@ -92,7 +92,7 @@ export const updateCommunity: MutationResolvers["updateCommunity"] = async ( socialMediaUrls: { facebook: args.data.socialMediaUrls.facebook, instagram: args.data.socialMediaUrls.facebook, - twitter: args.data.socialMediaUrls.twitter, + X: args.data.socialMediaUrls.X, linkedIn: args.data.socialMediaUrls.linkedIn, gitHub: args.data.socialMediaUrls.gitHub, youTube: args.data.socialMediaUrls.youTube, diff --git a/src/resolvers/Mutation/updateFundCampaignPledge.ts b/src/resolvers/Mutation/updateFundCampaignPledge.ts index a21cbc22f7..2090dff91c 100644 --- a/src/resolvers/Mutation/updateFundCampaignPledge.ts +++ b/src/resolvers/Mutation/updateFundCampaignPledge.ts @@ -1,10 +1,11 @@ +import { Types } from "mongoose"; import { FUNDRAISING_CAMPAIGN_PLEDGE_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR, } from "../../constants"; import { errors, requestContext } from "../../libraries"; import type { InterfaceUser } from "../../models"; -import { User } from "../../models"; +import { AppUserProfile, User } from "../../models"; import { FundraisingCampaignPledge, type InterfaceFundraisingCampaignPledges, @@ -82,6 +83,53 @@ export const updateFundraisingCampaignPledge: MutationResolvers["updateFundraisi USER_NOT_FOUND_ERROR.PARAM, ); } + + // Identify all users who were previously part of the pledge and were removed + const usersRemoved = pledge.users.filter( + (userId) => userId && !args.data.users?.includes(userId.toString()), + ); + + // Update AppUserProfile for every user who was removed from the pledge + for (const userId of usersRemoved) { + const updatedUserProfile = await AppUserProfile.findOneAndUpdate( + { userId }, + { $pull: { pledges: args.id } }, + { new: true }, + ); + + // Remove campaign from appUserProfile if there is no pledge left for that campaign. + const pledges = + updatedUserProfile?.pledges as InterfaceFundraisingCampaignPledges[]; + + const campaignId = pledge.campaign?.toString(); + const otherPledges = pledges.filter( + (p) => p.campaign?.toString() === campaignId, + ); + + if (otherPledges.length === 0) { + await AppUserProfile.findOneAndUpdate( + { userId }, + { $pull: { campaigns: campaignId } }, + { new: true }, + ); + } + } + + // Identify all users who are newly added to the pledge + const usersAdded = args.data.users.filter( + (userId) => + userId && !pledge.users.includes(new Types.ObjectId(userId)), + ); + + // Update AppUserProfile for every user who is newly added to the pledge + await AppUserProfile.updateMany( + { + userId: { $in: usersAdded }, + }, + { + $addToSet: { pledges: pledge._id, campaigns: pledge.campaign }, + }, + ); } const updatedPledge = await FundraisingCampaignPledge.findOneAndUpdate( diff --git a/src/resolvers/Mutation/updateNote.ts b/src/resolvers/Mutation/updateNote.ts index a0586850c6..52a7391907 100644 --- a/src/resolvers/Mutation/updateNote.ts +++ b/src/resolvers/Mutation/updateNote.ts @@ -17,6 +17,30 @@ import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Updates an existing note in the system. + * + * This function updates a specific note in the database. It first checks if the current user + * exists and if they have the proper profile. Then it verifies if the note exists and whether + * the current user is authorized to update it. If all checks pass, the function updates the note + * with the provided data. + * + * The function performs the following steps: + * 1. Retrieves the current user from the cache or database. + * 2. Verifies if the current user exists. + * 3. Retrieves the current user's profile from the cache or database. + * 4. Checks if the user has the necessary authorization to update the note. + * 5. Finds the note to be updated in the database. + * 6. Verifies that the note belongs to the current user. + * 7. Updates the note with the new data provided. + * + * @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 note to be updated and the new data. + * @param context - The context of the request, containing information about the currently authenticated user. + * + * @returns The updated note. + */ + export const updateNote: MutationResolvers["updateNote"] = async ( _parent, args, diff --git a/src/resolvers/Mutation/updatePost.ts b/src/resolvers/Mutation/updatePost.ts index 3229fe4357..82d3669a20 100644 --- a/src/resolvers/Mutation/updatePost.ts +++ b/src/resolvers/Mutation/updatePost.ts @@ -25,6 +25,17 @@ 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, @@ -86,7 +97,7 @@ export const updatePost: MutationResolvers["updatePost"] = async ( } } - // checks if there exists a post with _id === args.id + // Check if the post exists if (!post) { throw new errors.NotFoundError( requestContext.translate(POST_NOT_FOUND_ERROR.MESSAGE), @@ -95,6 +106,7 @@ export const updatePost: MutationResolvers["updatePost"] = async ( ); } + // 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( @@ -115,6 +127,7 @@ export const updatePost: MutationResolvers["updatePost"] = async ( ); } + // Handle image and video URL uploads if (args.data?.imageUrl && args.data?.imageUrl !== null) { args.data.imageUrl = await uploadEncodedImage( args.data.imageUrl, @@ -129,7 +142,7 @@ export const updatePost: MutationResolvers["updatePost"] = async ( ); } - // Check title and pinpost + // Validate title and pinned status if (args.data?.title && !post.pinned) { throw new errors.InputValidationError( requestContext.translate(POST_NEEDS_TO_BE_PINNED.MESSAGE), @@ -142,7 +155,7 @@ export const updatePost: MutationResolvers["updatePost"] = async ( ); } - // Checks if the recieved arguments are valid according to standard input norms + // Validate input lengths const validationResultTitle = isValidString(args.data?.title ?? "", 256); const validationResultText = isValidString(args.data?.text ?? "", 500); if (!validationResultTitle.isLessThanMaxLength) { @@ -162,6 +175,7 @@ export const updatePost: MutationResolvers["updatePost"] = async ( ); } + // Update the post in the database const updatedPost = await Post.findOneAndUpdate( { _id: args.id, diff --git a/src/resolvers/Mutation/updateUserPassword.ts b/src/resolvers/Mutation/updateUserPassword.ts index 0a793c8282..f490e970ab 100644 --- a/src/resolvers/Mutation/updateUserPassword.ts +++ b/src/resolvers/Mutation/updateUserPassword.ts @@ -15,6 +15,27 @@ import { deleteUserFromCache } from "../../services/UserCache/deleteUserFromCach import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Updates the password for the currently authenticated user. + * + * This function allows the current user to update their password. It performs the following steps: + * 1. Retrieves the current user from the cache or database. + * 2. Verifies the current user exists. + * 3. Retrieves the current user's profile from the cache or database. + * 4. Checks if the current user is authorized to update the password. + * 5. Validates the previous password provided by the user. + * 6. Ensures the new password and confirmation match. + * 7. Hashes the new password and updates it in the database. + * 8. Clears the user's token and updates their profile. + * 9. Updates the user and profile caches. + * + * @param _parent - This parameter is not used in this resolver function. + * @param args - The arguments provided by the GraphQL query, including the previous password, new password, and password confirmation. + * @param context - The context of the request, containing information about the currently authenticated user. + * + * @returns An object containing the updated user and their profile. + */ + export const updateUserPassword: MutationResolvers["updateUserPassword"] = async (_parent, args, context) => { let currentUser: InterfaceUser | null; diff --git a/src/resolvers/Mutation/updateUserTag.ts b/src/resolvers/Mutation/updateUserTag.ts index fbd560d337..794ca3fb15 100644 --- a/src/resolvers/Mutation/updateUserTag.ts +++ b/src/resolvers/Mutation/updateUserTag.ts @@ -14,7 +14,18 @@ import { findAppUserProfileCache } from "../../services/AppUserProfileCache/find import { cacheUsers } from "../../services/UserCache/cacheUser"; import { findUserInCache } from "../../services/UserCache/findUserInCache"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; - +/** + * Updates an existing tag's name based on provided input, including validation and authorization checks. + * + * This function updates the name of an existing tag in the database. It performs various checks to ensure that the current user is authorized to update the tag, validates that the new tag name is not the same as the old one, and ensures that no other tag with the same name exists under the same parent tag. It then updates the tag's name and returns the updated tag. + * + * @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 tag's `id` and the new `name` for the tag. + * @param context - Provides contextual information, including the current user's ID. This is used to authenticate and authorize the request. + * + * @returns The updated tag with its new name. + * + */ export const updateUserTag: MutationResolvers["updateUserTag"] = async ( _parent, args, @@ -63,7 +74,7 @@ export const updateUserTag: MutationResolvers["updateUserTag"] = async ( // Get the tag object const existingTag = await OrganizationTagUser.findOne({ - _id: args.input._id, + _id: args.input.tagId, }).lean(); if (!existingTag) { @@ -119,7 +130,7 @@ export const updateUserTag: MutationResolvers["updateUserTag"] = async ( // Update the title of the tag and return it return await OrganizationTagUser.findOneAndUpdate( { - _id: args.input._id, + _id: args.input.tagId, }, { name: args.input.name, diff --git a/src/resolvers/Organization/posts.ts b/src/resolvers/Organization/posts.ts index 55f0ba9cae..7aadc5f786 100644 --- a/src/resolvers/Organization/posts.ts +++ b/src/resolvers/Organization/posts.ts @@ -79,6 +79,10 @@ export const posts: OrganizationResolvers["posts"] = async ( }) .sort(sort) .limit(parsedArgs.limit) + .populate({ + path: "likedBy", + select: "image firstName lastName", + }) .lean() .exec(), diff --git a/src/resolvers/Organization/userTags.ts b/src/resolvers/Organization/userTags.ts index 6ccdf71d5b..2b4160cf36 100644 --- a/src/resolvers/Organization/userTags.ts +++ b/src/resolvers/Organization/userTags.ts @@ -72,6 +72,7 @@ export const userTags: OrganizationResolvers["userTags"] = async ( OrganizationTagUser.find({ ...filter, organizationId: parent._id, + parentTagId: null, }) .sort(sort) .limit(parsedArgs.limit) diff --git a/src/resolvers/Query/actionItemCategoriesByOrganization.ts b/src/resolvers/Query/actionItemCategoriesByOrganization.ts index ffb8c85e48..24e892207d 100644 --- a/src/resolvers/Query/actionItemCategoriesByOrganization.ts +++ b/src/resolvers/Query/actionItemCategoriesByOrganization.ts @@ -1,16 +1,23 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { ActionItemCategory } from "../../models"; +import { getWhere } from "./helperFunctions/getWhere"; +import { getSort } from "./helperFunctions/getSort"; /** * This query will fetch all categories for the organization from database. * @param _parent- - * @param args - An object that contains `organizationId` which is the _id of the Organization. + * @param args - An object that contains `organizationId` which is the _id of the Organization and `orderBy` which is the sorting order & where which is the filter. * @returns A `categories` object that holds all categories for the Organization. */ export const actionItemCategoriesByOrganization: QueryResolvers["actionItemCategoriesByOrganization"] = async (_parent, args) => { + const sort = getSort(args.orderBy); + const where = getWhere(args.where); const categories = await ActionItemCategory.find({ organizationId: args.organizationId, - }).lean(); + ...where, + }) + .sort(sort) + .lean(); return categories; }; diff --git a/src/resolvers/Query/actionItemsByEvent.ts b/src/resolvers/Query/actionItemsByEvent.ts index 08944db8b2..1c58845720 100644 --- a/src/resolvers/Query/actionItemsByEvent.ts +++ b/src/resolvers/Query/actionItemsByEvent.ts @@ -11,7 +11,7 @@ export const actionItemsByEvent: QueryResolvers["actionItemsByEvent"] = async ( args, ) => { const actionItems = await ActionItem.find({ - eventId: args.eventId, + event: args.eventId, }).lean(); return actionItems; diff --git a/src/resolvers/Query/actionItemsByOrganization.ts b/src/resolvers/Query/actionItemsByOrganization.ts index 696f3cdfda..828cf58005 100644 --- a/src/resolvers/Query/actionItemsByOrganization.ts +++ b/src/resolvers/Query/actionItemsByOrganization.ts @@ -1,6 +1,10 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; -import type { InterfaceActionItem } from "../../models"; -import { ActionItem, ActionItemCategory } from "../../models"; +import type { + InterfaceActionItem, + InterfaceActionItemCategory, + InterfaceUser, +} from "../../models"; +import { ActionItem } from "../../models"; import { getWhere } from "./helperFunctions/getWhere"; import { getSort } from "./helperFunctions/getSort"; /** @@ -11,23 +15,43 @@ import { getSort } from "./helperFunctions/getSort"; */ export const actionItemsByOrganization: QueryResolvers["actionItemsByOrganization"] = async (_parent, args) => { - const where = getWhere(args.where); const sort = getSort(args.orderBy); - - // Get the ids of all ActionItemCategories associated with the organization - const actionItemCategories = await ActionItemCategory.find({ - organizationId: args.organizationId, - }); - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, - ); + const where = getWhere(args.where); const actionItems = await ActionItem.find({ - actionItemCategoryId: { $in: actionItemCategoriesIds }, + organization: args.organizationId, + event: args.eventId, ...where, }) + .populate("creator") + .populate("assignee") + .populate("assigner") + .populate("actionItemCategory") + .populate("organization") + .populate("event") .sort(sort) .lean(); - return actionItems; + let filteredActionItems: InterfaceActionItem[] = actionItems; + + // Filter the action items based on category name + if (args.where?.categoryName) { + filteredActionItems = filteredActionItems.filter((item) => { + const tempItem = item as InterfaceActionItem; + const category = + tempItem.actionItemCategory as InterfaceActionItemCategory; + return category.name.includes(args?.where?.categoryName as string); + }); + } + + // Filter the action items based on assignee name + if (args.where?.assigneeName) { + filteredActionItems = filteredActionItems.filter((item) => { + const tempItem = item as InterfaceActionItem; + const assignee = tempItem.assignee as InterfaceUser; + return assignee.firstName.includes(args?.where?.assigneeName as string); + }); + } + + return filteredActionItems; }; diff --git a/src/resolvers/Query/advertisementsConnection.ts b/src/resolvers/Query/advertisementsConnection.ts index 621fa050d4..2b91038e0d 100644 --- a/src/resolvers/Query/advertisementsConnection.ts +++ b/src/resolvers/Query/advertisementsConnection.ts @@ -13,6 +13,18 @@ import { type ParseGraphQLConnectionCursorResult, } from "../../utilities/graphQLConnection"; +/** + * Retrieves a paginated list of advertisements based on the provided connection arguments. + * + * This function handles querying and pagination of advertisements using connection arguments. It performs validation of the connection arguments, applies filters and sorting, and then returns a paginated result containing the advertisements. The media URLs for each advertisement are adjusted based on the API root URL provided in the context. + * + * @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 query, including pagination and filter criteria. + * @param context - Provides contextual information, including the API root URL. This is used to construct the media URLs for the advertisements. + * + * @returns A paginated connection object containing the advertisements, their total count, and the pagination information. + * + */ export const advertisementsConnection: QueryResolvers["advertisementsConnection"] = async (_parent, args, context) => { const parseGraphQLConnectionArgumentsResult = @@ -74,15 +86,22 @@ export const advertisementsConnection: QueryResolvers["advertisementsConnection" }); }; -/* -This is typescript type of the parsed cursor for this connection resolver. -*/ +/** + * Type representing the parsed cursor used in the connection resolver. + */ type ParsedCursor = string; -/* -This function is used to validate and transform the cursor passed to this connnection -resolver. -*/ +/** + * Validates and transforms the cursor passed to the connection resolver. + * + * This function checks if the provided cursor value corresponds to a valid advertisement in the database. If the cursor is valid, it is returned as-is. Otherwise, an error is recorded. + * + * @param cursorValue - The value of the cursor to be validated. + * @param cursorName - The name of the cursor argument used in the query. + * @param cursorPath - The path in the query where the cursor argument is located. + * + * @returns An object containing a flag indicating success or failure, the parsed cursor, and any errors encountered during validation. + */ export const parseCursor = async ({ cursorValue, cursorName, diff --git a/src/resolvers/Query/agendaItemById.ts b/src/resolvers/Query/agendaItemById.ts index 3e4513cf84..58cf6795e7 100644 --- a/src/resolvers/Query/agendaItemById.ts +++ b/src/resolvers/Query/agendaItemById.ts @@ -2,6 +2,17 @@ import { AgendaItemModel } from "../../models"; import { errors } from "../../libraries"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { AGENDA_ITEM_NOT_FOUND_ERROR } from "../../constants"; +/** + * Retrieves an agenda item by its ID. + * + * This function fetches a specific agenda item from the database using its ID. If the agenda item + * 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 agenda item to retrieve. + * + * @returns The agenda item with the specified ID. + */ export const getAgendaItem: QueryResolvers["getAgendaItem"] = async ( _parent, diff --git a/src/resolvers/Query/eventsByOrganizationConnection.ts b/src/resolvers/Query/eventsByOrganizationConnection.ts index 73ee2cf55f..e184a2bc47 100644 --- a/src/resolvers/Query/eventsByOrganizationConnection.ts +++ b/src/resolvers/Query/eventsByOrganizationConnection.ts @@ -4,6 +4,19 @@ 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. + * + * This function performs the following steps: + * 1. Generates recurring event instances up to a certain date if the organization has any. + * 2. Builds a query filter (`where`) and sorting parameters based on the provided arguments. + * 3. Queries the database for events matching the filter, with sorting, pagination, and related data fetching. + * + * @param _parent - This parameter is not used in this resolver function. + * @param args - The arguments provided by the GraphQL query, including filters (`where`), sorting order (`orderBy`), pagination options (`first` and `skip`), and any other query parameters. + * + * @returns A list of events matching the query parameters, with related data populated. + */ export const eventsByOrganizationConnection: QueryResolvers["eventsByOrganizationConnection"] = async (_parent, args) => { diff --git a/src/resolvers/Query/fundsByOrganization.ts b/src/resolvers/Query/fundsByOrganization.ts index 11628fd1c7..b6543b771a 100644 --- a/src/resolvers/Query/fundsByOrganization.ts +++ b/src/resolvers/Query/fundsByOrganization.ts @@ -3,6 +3,19 @@ import { Fund } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { getSort } from "./helperFunctions/getSort"; import { getWhere } from "./helperFunctions/getWhere"; +/** + * Retrieves funds associated with a specific organization based on the provided query parameters. + * + * This function performs the following steps: + * 1. Builds a query filter (`where`) and sorting parameters based on the provided arguments. + * 2. Queries the database for funds associated with the specified organization ID and matching the filter criteria. + * 3. Sorts the results based on the provided sorting order. + * + * @param _parent - This parameter is not used in this resolver function. + * @param args - The arguments provided by the GraphQL query, including the organization ID (`organizationId`), filter criteria (`where`), and sorting order (`orderBy`). + * + * @returns A list of funds associated with the specified organization, matching the filter and sorting criteria. + */ export const fundsByOrganization: QueryResolvers["fundsByOrganization"] = async (_parent, args) => { diff --git a/src/resolvers/Query/getAllAgendaItems.ts b/src/resolvers/Query/getAllAgendaItems.ts index 6f2552d139..04604cc105 100644 --- a/src/resolvers/Query/getAllAgendaItems.ts +++ b/src/resolvers/Query/getAllAgendaItems.ts @@ -1,6 +1,19 @@ import { AgendaItemModel } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Retrieves all agenda items from the database. + * + * This function performs the following steps: + * 1. Fetches all agenda items stored in the database. + * 2. Returns the list of all agenda items. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param _args - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * + * @returns A list of all agenda items stored in the database. + */ + export const getAllAgendaItems: QueryResolvers["getAllAgendaItems"] = async ( _parent, _args, diff --git a/src/resolvers/Query/getAllNotesForAgendaItem.ts b/src/resolvers/Query/getAllNotesForAgendaItem.ts index e0aac994c6..5917ddd385 100644 --- a/src/resolvers/Query/getAllNotesForAgendaItem.ts +++ b/src/resolvers/Query/getAllNotesForAgendaItem.ts @@ -1,6 +1,18 @@ import type { InterfaceNote } from "../../models"; import { NoteModel } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +/** + * Retrieves all notes associated with a specific agenda item from the database. + * + * This function performs the following steps: + * 1. Queries the database for notes that are associated with the specified agenda item ID. + * 2. Returns the list of notes for the given agenda item. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param args - The arguments provided by the GraphQL query, including the agenda item ID (`agendaItemId`) for which notes are to be retrieved. + * + * @returns A list of notes associated with the specified agenda item. + */ export const getAllNotesForAgendaItem: QueryResolvers["getAllNotesForAgendaItem"] = async (_parent, args) => { diff --git a/src/resolvers/Query/getDonationByOrgIdConnection.ts b/src/resolvers/Query/getDonationByOrgIdConnection.ts index 210945bb53..4e276079c6 100644 --- a/src/resolvers/Query/getDonationByOrgIdConnection.ts +++ b/src/resolvers/Query/getDonationByOrgIdConnection.ts @@ -2,6 +2,24 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import type { InterfaceDonation } from "../../models"; import { Donation } from "../../models"; import { getWhere } from "./helperFunctions/getWhere"; +/** + * Retrieves a paginated list of donations associated with a specific organization from the database. + * + * This function performs the following steps: + * 1. Constructs a query filter using the provided criteria and organization ID. + * 2. Queries the database for donations that match the criteria and belong to the specified organization. + * 3. Applies pagination by limiting and skipping the results based on the provided arguments. + * 4. Returns the list of donations that match the query. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param args - The arguments provided by the GraphQL query, including: + * - `orgId`: The ID of the organization for which donations are being retrieved. + * - `where`: Optional filter criteria to apply to the donations. + * - `first`: The maximum number of donation records to return (for pagination). + * - `skip`: The number of donation records to skip (for pagination). + * + * @returns A list of donations associated with the specified organization and matching the provided filter criteria. + */ export const getDonationByOrgIdConnection: QueryResolvers["getDonationByOrgIdConnection"] = async (_parent, args) => { diff --git a/src/resolvers/Query/getEventAttendee.ts b/src/resolvers/Query/getEventAttendee.ts index fb5c496bee..817cab1d3c 100644 --- a/src/resolvers/Query/getEventAttendee.ts +++ b/src/resolvers/Query/getEventAttendee.ts @@ -1,6 +1,21 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { EventAttendee } from "../../models"; +/** + * Retrieves an attendee record for a specific event and user from the database. + * + * This function performs the following steps: + * 1. Queries the database to find an `EventAttendee` record that matches the provided event ID and user ID. + * 2. Returns the found attendee record, or `null` if no matching record is found. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param args - The arguments provided by the GraphQL query, including: + * - `eventId`: The ID of the event for which the attendee is being retrieved. + * - `userId`: The ID of the user for whom the attendee record is being retrieved. + * + * @returns The attendee record for the specified event and user, or `null` if no record is found. + */ + export const getEventAttendee: QueryResolvers["getEventAttendee"] = async ( _parent, args, diff --git a/src/resolvers/Query/getEventAttendeesByEventId.ts b/src/resolvers/Query/getEventAttendeesByEventId.ts index 9c4f7fadf7..0784bd993c 100644 --- a/src/resolvers/Query/getEventAttendeesByEventId.ts +++ b/src/resolvers/Query/getEventAttendeesByEventId.ts @@ -1,6 +1,18 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { EventAttendee } from "../../models"; - +/** + * Retrieves all attendees for a specific event from the database. + * + * This function performs the following steps: + * 1. Queries the database to find all `EventAttendee` records that match the provided event ID. + * 2. Returns an array of attendee records for the specified event. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param args - The arguments provided by the GraphQL query, including: + * - `eventId`: The ID of the event for which attendees are being retrieved. + * + * @returns An array of attendee records for the specified event. + */ export const getEventAttendeesByEventId: QueryResolvers["getEventAttendeesByEventId"] = async (_parent, args) => { const eventAttendees = await EventAttendee.find({ diff --git a/src/resolvers/Query/getEventVolunteerGroups.ts b/src/resolvers/Query/getEventVolunteerGroups.ts new file mode 100644 index 0000000000..bb5e7d558e --- /dev/null +++ b/src/resolvers/Query/getEventVolunteerGroups.ts @@ -0,0 +1,22 @@ +import { EventVolunteerGroup } from "../../models"; +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { getWhere } from "./helperFunctions/getWhere"; +/** + * This query will fetch eventVolunteerGroups as a transaction from database. + * @param _parent- + * @param args - An object that contains where object for eventVolunteerGroups. + * @returns An array of `eventVolunteerGroup` object. + */ +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"); + + return eventVolunteerGroups; + }; diff --git a/src/resolvers/Query/getFundraisingCampaign.ts b/src/resolvers/Query/getFundraisingCampaigns.ts similarity index 51% rename from src/resolvers/Query/getFundraisingCampaign.ts rename to src/resolvers/Query/getFundraisingCampaigns.ts index deb2dd362c..108225a558 100644 --- a/src/resolvers/Query/getFundraisingCampaign.ts +++ b/src/resolvers/Query/getFundraisingCampaigns.ts @@ -1,21 +1,22 @@ -import { - FundraisingCampaign, - type InterfaceFundraisingCampaign, -} from "../../models"; +import { FundraisingCampaign } from "../../models"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { getSort } from "./helperFunctions/getSort"; +import { getWhere } from "./helperFunctions/getWhere"; /** * This query will fetch the fundraisingCampaign as a transaction from database. * @param _parent- * @param args - An object that contains `id` of the campaign. * @returns A `fundraisingCampaign` object. - */ //@ts-expect-error - type error -export const getFundraisingCampaignById: QueryResolvers["getFundraisingCampaignById"] = + */ +export const getFundraisingCampaigns: QueryResolvers["getFundraisingCampaigns"] = async (_parent, args) => { - const sort = getSort(args.orderBy); - const campaign = await FundraisingCampaign.findOne({ - _id: args.id, + const sortPledge = getSort(args.pledgeOrderBy); + const sortCampaign = getSort(args.campaignOrderby); + const where = getWhere(args.where); + const campaigns = await FundraisingCampaign.find({ + ...where, }) + .sort(sortCampaign) .populate("fundId") .populate({ path: "pledges", @@ -23,10 +24,9 @@ export const getFundraisingCampaignById: QueryResolvers["getFundraisingCampaignB path: "users", }, options: { - sort: sort, + sort: sortPledge, }, - }) - .lean(); + }); - return campaign ?? ({} as InterfaceFundraisingCampaign); + return campaigns; }; diff --git a/src/resolvers/Query/getNoteById.ts b/src/resolvers/Query/getNoteById.ts index 34a1f39aae..9e07493e51 100644 --- a/src/resolvers/Query/getNoteById.ts +++ b/src/resolvers/Query/getNoteById.ts @@ -3,6 +3,21 @@ import { NoteModel } from "../../models"; import { errors } from "../../libraries"; import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { NOTE_NOT_FOUND_ERROR } from "../../constants"; +/** + * Retrieves a note by its ID from the database. + * + * This function performs the following steps: + * 1. Queries the database to find a `Note` record by the provided ID. + * 2. If the note is not found, throws a `NotFoundError` with a predefined error message. + * 3. Returns the note record if found. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param args - The arguments provided by the GraphQL query, including: + * - `id`: The ID of the note to be retrieved. + * + * @returns The note record corresponding to the provided ID. + * + */ export const getNoteById: QueryResolvers["getNoteById"] = async ( _parent, diff --git a/src/resolvers/Query/getPledgesByUserId.ts b/src/resolvers/Query/getPledgesByUserId.ts new file mode 100644 index 0000000000..c1c37625a7 --- /dev/null +++ b/src/resolvers/Query/getPledgesByUserId.ts @@ -0,0 +1,67 @@ +import { USER_NOT_AUTHORIZED_ERROR } from "../../constants"; +import { errors, requestContext } from "../../libraries"; +import type { InterfaceFundraisingCampaign, InterfaceUser } from "../../models"; +import { AppUserProfile } from "../../models"; +import { type InterfaceFundraisingCampaignPledges } from "../../models/FundraisingCampaignPledge"; +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { getSort } from "./helperFunctions/getSort"; + +/** + * This query will fetch the fundraisingCampaignPledge as a transaction from database. + * @param _parent- + * @param args - An object that contains `id` of the fund. + * @returns An array of `fundraisingCampaignPledge` object. + */ +export const getPledgesByUserId: QueryResolvers["getPledgesByUserId"] = async ( + _parent, + args, +) => { + const sort = getSort(args.orderBy); + + const appUserProfile = await AppUserProfile.findOne({ + userId: args.userId, + }).populate({ + path: "pledges", + options: { + sort, + }, + populate: [ + { + path: "campaign", + }, + { + path: "users", + }, + ], + }); + + if (!appUserProfile) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE), + USER_NOT_AUTHORIZED_ERROR.CODE, + USER_NOT_AUTHORIZED_ERROR.PARAM, + ); + } + + // Filter the pledges based on campaign name + if (args.where?.name_contains) { + appUserProfile.pledges = appUserProfile.pledges.filter((pledge) => { + const tempPledge = pledge as InterfaceFundraisingCampaignPledges; + const campaign = tempPledge.campaign as InterfaceFundraisingCampaign; + return campaign.name.includes(args?.where?.name_contains as string); + }); + } + + // Filter the pledges based on pledger's name + if (args.where?.firstName_contains) { + appUserProfile.pledges = appUserProfile.pledges.filter((pledge) => { + const tempPledge = pledge as InterfaceFundraisingCampaignPledges; + const users = tempPledge.users as InterfaceUser[]; + return users.some((user) => + user.firstName.includes(args?.where?.firstName_contains as string), + ); + }); + } + + return appUserProfile.pledges as InterfaceFundraisingCampaignPledges[]; +}; diff --git a/src/resolvers/Query/getUserTag.ts b/src/resolvers/Query/getUserTag.ts new file mode 100644 index 0000000000..608c2882d8 --- /dev/null +++ b/src/resolvers/Query/getUserTag.ts @@ -0,0 +1,33 @@ +import { OrganizationTagUser } from "../../models"; +import { errors, requestContext } from "../../libraries"; +import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; +import { TAG_NOT_FOUND } from "../../constants"; + +/** + * Retrieves a user tag by its ID. + * + * This function fetches a specific user tag from the database using its ID. 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 user tag to retrieve. + * + * @returns The user tag with the specified ID. + */ + +export const getUserTag: QueryResolvers["getUserTag"] = async ( + _parent, + args, +) => { + const userTag = await OrganizationTagUser.findById(args.id).lean(); + + if (!userTag) { + throw new errors.NotFoundError( + requestContext.translate(TAG_NOT_FOUND.MESSAGE), + TAG_NOT_FOUND.CODE, + TAG_NOT_FOUND.PARAM, + ); + } + + return userTag; +}; diff --git a/src/resolvers/Query/getUserTagAncestors.ts b/src/resolvers/Query/getUserTagAncestors.ts new file mode 100644 index 0000000000..cf516eb43e --- /dev/null +++ b/src/resolvers/Query/getUserTagAncestors.ts @@ -0,0 +1,45 @@ +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/getVenueByOrgId.ts b/src/resolvers/Query/getVenueByOrgId.ts index f40dfc50af..1b8d3a60f5 100644 --- a/src/resolvers/Query/getVenueByOrgId.ts +++ b/src/resolvers/Query/getVenueByOrgId.ts @@ -3,6 +3,26 @@ import type { InterfaceVenue } from "../../models"; import { Venue } from "../../models"; import { getWhere } from "./helperFunctions/getWhere"; import { getSort } from "./helperFunctions/getSort"; +/** + * Retrieves venues associated with a specific organization, with optional filtering and sorting. + * + * This function performs the following steps: + * 1. Constructs the query filter using the `getWhere` helper function based on provided filter criteria. + * 2. Determines the sorting order using the `getSort` helper function based on provided sort criteria. + * 3. Queries the `Venue` collection in the database to find venues that match the specified organization ID and any additional filter criteria. + * 4. Limits the number of results based on the `first` argument and skips results based on the `skip` argument. + * 5. Sorts the results according to the specified sort criteria. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param args - The arguments provided by the GraphQL query, including: + * - `orgId`: The ID of the organization for which venues are being retrieved. + * - `where`: Optional filter criteria to apply to the venue query. + * - `orderBy`: Optional sorting criteria for the results. + * - `first`: Optional limit on the number of results to return. + * - `skip`: Optional number of results to skip for pagination. + * + * @returns A promise that resolves to an array of venues matching the query criteria. + */ export const getVenueByOrgId: QueryResolvers["getVenueByOrgId"] = async ( _parent, diff --git a/src/resolvers/Query/hasSubmittedFeedback.ts b/src/resolvers/Query/hasSubmittedFeedback.ts index 803270b4e7..25314a1bfd 100644 --- a/src/resolvers/Query/hasSubmittedFeedback.ts +++ b/src/resolvers/Query/hasSubmittedFeedback.ts @@ -7,7 +7,18 @@ import { USER_NOT_CHECKED_IN, USER_NOT_REGISTERED_FOR_EVENT, } from "../../constants"; - +/** + * Checks whether a user has submitted feedback for a specific event. + * + * This function verifies if the given user and event exist in the database. It then checks if the user is registered and checked in for the event. Finally, it determines whether the user has submitted feedback for that event based on the check-in record. + * + * @param _parent - This parameter represents the parent resolver in the GraphQL schema and is not used in this function. + * @param args - The arguments provided to the GraphQL query. Should include: + * - `userId` (string): The ID of the user to check. + * - `eventId` (string): The ID of the event to check. + * + * @returns A boolean value indicating whether the user has submitted feedback for the event. This is determined by checking the `feedbackSubmitted` property of the check-in record. + */ export const hasSubmittedFeedback: QueryResolvers["hasSubmittedFeedback"] = async (_parent, args) => { const currentUserExists = await User.exists({ diff --git a/src/resolvers/Query/helperFunctions/getSort.ts b/src/resolvers/Query/helperFunctions/getSort.ts index 6ebaeeac7c..d3f68a704c 100644 --- a/src/resolvers/Query/helperFunctions/getSort.ts +++ b/src/resolvers/Query/helperFunctions/getSort.ts @@ -9,6 +9,7 @@ import type { PledgeOrderByInput, CampaignOrderByInput, FundOrderByInput, + ActionItemsOrderByInput, } from "../../../types/generatedGraphQLTypes"; export const getSort = ( @@ -22,6 +23,7 @@ export const getSort = ( | FundOrderByInput | CampaignOrderByInput | PledgeOrderByInput + | ActionItemsOrderByInput > | undefined, ): @@ -320,6 +322,19 @@ export const getSort = ( commentCount: -1, }; break; + + case "dueDate_ASC": + sortPayload = { + dueDate: 1, + }; + break; + + case "dueDate_DESC": + sortPayload = { + dueDate: -1, + }; + break; + default: break; } diff --git a/src/resolvers/Query/helperFunctions/getWhere.ts b/src/resolvers/Query/helperFunctions/getWhere.ts index 9c5c795460..e2288ee6cb 100644 --- a/src/resolvers/Query/helperFunctions/getWhere.ts +++ b/src/resolvers/Query/helperFunctions/getWhere.ts @@ -3,6 +3,7 @@ import type { ActionItemWhereInput, DonationWhereInput, EventWhereInput, + EventVolunteerGroupWhereInput, FundWhereInput, InputMaybe, OrganizationWhereInput, @@ -10,6 +11,8 @@ import type { UserWhereInput, VenueWhereInput, CampaignWhereInput, + PledgeWhereInput, + ActionItemCategoryWhereInput, } from "../../../types/generatedGraphQLTypes"; /** @@ -30,13 +33,16 @@ export const getWhere = ( | InputMaybe< Partial< EventWhereInput & + EventVolunteerGroupWhereInput & OrganizationWhereInput & PostWhereInput & UserWhereInput & DonationWhereInput & ActionItemWhereInput & + ActionItemCategoryWhereInput & CampaignWhereInput & FundWhereInput & + PledgeWhereInput & VenueWhereInput > > @@ -196,27 +202,19 @@ export const getWhere = ( }; } - // Return action items that are active - if (where.is_active) { - wherePayload = { - ...wherePayload, - isCompleted: false, - }; - } - // Return action items that are completed - if (where.is_completed) { + if (where.is_completed !== undefined) { wherePayload = { ...wherePayload, - isCompleted: true, + isCompleted: where.is_completed, }; } // Return action items belonging to a specific event - if (where.event_id) { + if (where.event_id || where.eventId) { wherePayload = { ...wherePayload, - eventId: where.event_id, + eventId: where.event_id || where.eventId, }; } @@ -344,7 +342,7 @@ export const getWhere = ( }; } - // Returns organizations with name containing provided string + // Returns objects with name containing provided string if (where.name_contains) { wherePayload = { ...wherePayload, @@ -352,7 +350,7 @@ export const getWhere = ( }; } - // Returns organizations with name starts with that provided string + // Returns objects where name starts with provided string if (where.name_starts_with) { const regexp = new RegExp("^" + where.name_starts_with); wherePayload = { @@ -742,23 +740,47 @@ export const getWhere = ( }; } - if (where.name_starts_with) { - const regexp = new RegExp("^" + where.name_starts_with); + // Returns objects with provided fundId condition + if (where.fundId) { wherePayload = { ...wherePayload, - name: regexp, + fundId: where.fundId, }; } - if (where.name_contains) { + // Returns object with provided organizationId condition + if (where.organizationId) { wherePayload = { ...wherePayload, - name: { - $regex: where.name_contains, - $options: "i", + organizationId: where.organizationId, + }; + } + + // Returns object with provided campaignId condition + if (where.campaignId) { + wherePayload = { + ...wherePayload, + _id: where.campaignId, + }; + } + + // Returns objects where volunteerId is present in volunteers list + if (where.volunteerId) { + wherePayload = { + ...wherePayload, + volunteers: { + $in: [where.volunteerId], }, }; } + // Returns object with provided is_disabled condition + if (where.is_disabled !== undefined) { + wherePayload = { + ...wherePayload, + isDisabled: where.is_disabled, + }; + } + return wherePayload; }; diff --git a/src/resolvers/Query/index.ts b/src/resolvers/Query/index.ts index 55c74a7bca..fc878303d8 100644 --- a/src/resolvers/Query/index.ts +++ b/src/resolvers/Query/index.ts @@ -21,6 +21,7 @@ import { groupChatsByUserId } from "./groupChatsByUserId"; import { event } from "./event"; import { eventsByOrganization } from "./eventsByOrganization"; import { eventsByOrganizationConnection } from "./eventsByOrganizationConnection"; +import { getEventVolunteerGroups } from "./getEventVolunteerGroups"; import { fundsByOrganization } from "./fundsByOrganization"; import { getAllAgendaItems } from "./getAllAgendaItems"; import { getEventInvitesByUserId } from "./getEventInvitesByUserId"; @@ -29,9 +30,12 @@ import { getDonationById } from "./getDonationById"; import { getDonationByOrgId } from "./getDonationByOrgId"; import { getDonationByOrgIdConnection } from "./getDonationByOrgIdConnection"; import { getFundById } from "./getFundById"; -import { getFundraisingCampaignById } from "./getFundraisingCampaign"; +import { getFundraisingCampaigns } from "./getFundraisingCampaigns"; +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"; @@ -77,10 +81,13 @@ export const Query: QueryResolvers = { getDonationByOrgId, getDonationByOrgIdConnection, getEventInvitesByUserId, + getEventVolunteerGroups, getAllNotesForAgendaItem, getNoteById, getlanguage, getPlugins, + getUserTag, + getUserTagAncestors, isSampleOrganization, me, myLanguage, @@ -94,9 +101,10 @@ export const Query: QueryResolvers = { users, usersConnection, getFundById, - getFundraisingCampaignById, + getFundraisingCampaigns, venue, fundsByOrganization, + getPledgesByUserId, getEventAttendee, getEventAttendeesByEventId, getVenueByOrgId, diff --git a/src/resolvers/Query/me.ts b/src/resolvers/Query/me.ts index 9c269818da..6b8fbbc792 100644 --- a/src/resolvers/Query/me.ts +++ b/src/resolvers/Query/me.ts @@ -42,6 +42,8 @@ export const me: QueryResolvers["me"] = async (_parent, _args, context) => { .populate("createdEvents") .populate("eventAdmin") .populate("adminFor") + .populate("pledges") + .populate("campaigns") .lean(); if (!userAppProfile) { throw new errors.NotFoundError( diff --git a/src/resolvers/Query/organizationIsSample.ts b/src/resolvers/Query/organizationIsSample.ts index bc13f70f64..5e20ccfd22 100644 --- a/src/resolvers/Query/organizationIsSample.ts +++ b/src/resolvers/Query/organizationIsSample.ts @@ -2,6 +2,21 @@ import type { QueryResolvers } from "../../types/generatedGraphQLTypes"; import { Organization, SampleData } from "../../models"; import { errors, requestContext } from "../../libraries"; import { ORGANIZATION_NOT_FOUND_ERROR } from "../../constants"; +/** + * Checks whether the specified organization is a sample organization. + * + * This function performs the following steps: + * 1. Retrieves the organization from the database using the provided organization ID. + * 2. If the organization is not found, throws an unauthorized error. + * 3. Searches for a sample document associated with the organization ID in the `SampleData` collection. + * 4. Returns `true` if the sample document is found, indicating the organization is a sample organization; otherwise, returns `false`. + * + * @param _parent - This parameter is not used in this resolver function but is included for compatibility with GraphQL resolver signatures. + * @param args - The arguments provided by the GraphQL query, including: + * - `id`: The ID of the organization to check. + * + * @returns A promise that resolves to `true` if the organization is a sample organization, otherwise `false`. + */ export const isSampleOrganization: QueryResolvers["isSampleOrganization"] = async (_parent, args) => { diff --git a/src/resolvers/Query/organizationsMemberConnection.ts b/src/resolvers/Query/organizationsMemberConnection.ts index 76a85c4d88..0a0c29c2dc 100644 --- a/src/resolvers/Query/organizationsMemberConnection.ts +++ b/src/resolvers/Query/organizationsMemberConnection.ts @@ -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, @@ -144,6 +145,7 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC } else { users = usersModel.docs.map((user) => ({ _id: user._id, + identifier: user.identifier, appUserProfileId: user.appUserProfileId, address: { city: user.address?.city, diff --git a/src/resolvers/Query/user.ts b/src/resolvers/Query/user.ts index 36f0e0f9d6..c0716be0c3 100644 --- a/src/resolvers/Query/user.ts +++ b/src/resolvers/Query/user.ts @@ -35,6 +35,8 @@ export const user: QueryResolvers["user"] = async (_parent, args, context) => { .populate("createdEvents") .populate("eventAdmin") .populate("adminFor") + .populate("pledges") + .populate("campaigns") .lean()) as InterfaceAppUserProfile; // This Query field doesn't allow client to see organizations they are blocked by diff --git a/src/resolvers/Query/users.ts b/src/resolvers/Query/users.ts index 1ea4600a61..ce65967e80 100644 --- a/src/resolvers/Query/users.ts +++ b/src/resolvers/Query/users.ts @@ -53,7 +53,6 @@ export const users: QueryResolvers["users"] = async ( .limit(args.first ?? 0) .skip(args.skip ?? 0) .select(["-password"]) - .populate("joinedOrganizations") .populate("registeredEvents") .populate("organizationsBlockedBy") @@ -66,7 +65,9 @@ export const users: QueryResolvers["users"] = async ( .populate("createdOrganizations") .populate("createdEvents") .populate("eventAdmin") - .populate("adminFor"); + .populate("adminFor") + .populate("pledges") + .populate("campaigns"); return { user: { @@ -84,6 +85,8 @@ export const users: QueryResolvers["users"] = async ( createdOrganizations: [], createdEvents: [], eventAdmin: [], + pledges: [], + campaigns: [], }, }; }), diff --git a/src/resolvers/Query/usersConnection.ts b/src/resolvers/Query/usersConnection.ts index 85e2201617..c87b2d0eab 100644 --- a/src/resolvers/Query/usersConnection.ts +++ b/src/resolvers/Query/usersConnection.ts @@ -37,6 +37,8 @@ export const usersConnection: QueryResolvers["usersConnection"] = async ( .populate("createdEvents") .populate("eventAdmin") .populate("adminFor") + .populate("pledges") + .populate("campaigns") .lean(); return { user: user as InterfaceUser, diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index 01f58e313a..38873057ba 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -12,7 +12,6 @@ import { } from "graphql-scalars"; import GraphQLUpload from "graphql-upload/GraphQLUpload.mjs"; import type { Resolvers } from "../types/generatedGraphQLTypes"; -import { ActionItem } from "./ActionItem"; import { ActionItemCategory } from "./ActionItemCategory"; import { AgendaItem } from "./AgendaItem"; import { AgendaSection } from "./AgendaSection"; @@ -44,7 +43,6 @@ import { Advertisement } from "./Advertisement"; import { currentUserExists } from "./middleware/currentUserExists"; const resolvers: Resolvers = { - ActionItem, ActionItemCategory, AgendaItem, AgendaSection, diff --git a/src/setup/getMinioBinaryUrl.ts b/src/setup/getMinioBinaryUrl.ts new file mode 100644 index 0000000000..08f05c9a06 --- /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 0000000000..1cfedffd8e --- /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 0000000000..7c2ac411f3 --- /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 0000000000..7d88fd4573 --- /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 d1184a5f92..aeef732b1f 100644 --- a/src/setup/superAdmin.ts +++ b/src/setup/superAdmin.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import inquirer from "inquirer"; import { isValidEmail } from "./isValidEmail"; diff --git a/src/setup/updateIgnoreFile.ts b/src/setup/updateIgnoreFile.ts new file mode 100644 index 0000000000..944e5ac1e5 --- /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 a07f8c8741..b3e0a8b4c2 100644 --- a/src/typeDefs/enums.ts +++ b/src/typeDefs/enums.ts @@ -5,6 +5,8 @@ export const enums = gql` enum ActionItemsOrderByInput { createdAt_ASC createdAt_DESC + dueDate_ASC + dueDate_DESC } enum EventOrderByInput { diff --git a/src/typeDefs/inputs.ts b/src/typeDefs/inputs.ts index e209e9a09d..65cca30e4d 100644 --- a/src/typeDefs/inputs.ts +++ b/src/typeDefs/inputs.ts @@ -42,6 +42,7 @@ export const inputs = gql` input CreateActionItemInput { assigneeId: ID! preCompletionNotes: String + allotedHours: Float dueDate: Date eventId: ID } @@ -74,10 +75,16 @@ export const inputs = gql` input ActionItemWhereInput { actionItemCategory_id: ID event_id: ID - is_active: Boolean + categoryName: String + assigneeName: String is_completed: Boolean } + input ActionItemCategoryWhereInput { + name_contains: String + is_disabled: Boolean + } + input CreateAgendaCategoryInput { name: String! description: String @@ -151,6 +158,12 @@ export const inputs = gql` volunteersRequired: Int } + input EventVolunteerGroupWhereInput { + eventId: ID + volunteerId: ID + name_contains: String + } + input UpdateEventVolunteerInput { eventId: ID isAssigned: Boolean @@ -222,6 +235,7 @@ export const inputs = gql` endDate: Date! fundingGoal: Float! currency: Currency! + organizationId: ID! } input FundCampaignPledgeInput { campaignId: ID! @@ -237,6 +251,16 @@ export const inputs = gql` } input CampaignWhereInput { + id: ID + fundId: ID + organizationId: ID + name_contains: String + } + + input PledgeWhereInput { + id: ID + campaignId: ID + firstName_contains: String name_contains: String } @@ -391,7 +415,7 @@ export const inputs = gql` linkedIn: String reddit: String slack: String - twitter: String + X: String youTube: String } @@ -406,6 +430,7 @@ export const inputs = gql` postCompletionNotes: String dueDate: Date completionDate: Date + allotedHours: Float isCompleted: Boolean } @@ -473,8 +498,8 @@ export const inputs = gql` } input UpdateUserTagInput { - _id: ID! - tagColor: String! + tagId: ID! + tagColor: String name: String! } diff --git a/src/typeDefs/mutations.ts b/src/typeDefs/mutations.ts index c133e8ba9d..bca74770e5 100644 --- a/src/typeDefs/mutations.ts +++ b/src/typeDefs/mutations.ts @@ -76,6 +76,7 @@ export const mutations = gql` createActionItemCategory( name: String! + isDisabled: Boolean! organizationId: ID! ): ActionItemCategory! @auth @@ -209,8 +210,6 @@ export const mutations = gql` removeAgendaItem(id: ID!): AgendaItem! removeEventVolunteer(id: ID!): EventVolunteer! @auth - removeFund(id: ID!): Fund! @auth - removeFundraisingCampaign(id: ID!): FundraisingCampaign! @auth removeFundraisingCampaignPledge(id: ID!): FundraisingCampaignPledge! @auth removeEventVolunteerGroup(id: ID!): EventVolunteerGroup! @auth diff --git a/src/typeDefs/queries.ts b/src/typeDefs/queries.ts index 80f0c4acaa..4db8616859 100644 --- a/src/typeDefs/queries.ts +++ b/src/typeDefs/queries.ts @@ -11,12 +11,15 @@ export const queries = gql` actionItemsByOrganization( organizationId: ID! + eventId: ID where: ActionItemWhereInput orderBy: ActionItemsOrderByInput ): [ActionItem] actionItemCategoriesByOrganization( organizationId: ID! + where: ActionItemCategoryWhereInput + orderBy: ActionItemsOrderByInput ): [ActionItemCategory] agendaItemByEvent(relatedEventId: ID!): [AgendaItem] @@ -64,6 +67,10 @@ export const queries = gql` eventVolunteersByEvent(id: ID!): [EventVolunteer] + getEventVolunteerGroups( + where: EventVolunteerGroupWhereInput + ): [EventVolunteerGroup]! + fundsByOrganization( organizationId: ID! where: FundWhereInput @@ -77,17 +84,27 @@ export const queries = gql` getEventAttendee(userId: ID!, eventId: ID!): EventAttendee getEventInvitesByUserId(userId: ID!): [EventAttendee!]! + getFundById( id: ID! orderBy: CampaignOrderByInput where: CampaignWhereInput ): Fund! - getFundraisingCampaignById( - id: ID! - orderBy: PledgeOrderByInput - ): FundraisingCampaign! + + getFundraisingCampaigns( + where: CampaignWhereInput + pledgeOrderBy: PledgeOrderByInput + campaignOrderby: CampaignOrderByInput + ): [FundraisingCampaign]! + getFundraisingCampaignPledgeById(id: ID!): FundraisingCampaignPledge! + getPledgesByUserId( + userId: ID! + where: PledgeWhereInput + orderBy: PledgeOrderByInput + ): [FundraisingCampaignPledge] + getDonationByOrgId(orgId: ID!): [Donation] getDonationByOrgIdConnection( @@ -111,6 +128,10 @@ export const queries = gql` getNoteById(id: ID!): Note! + getUserTag(id: ID!): UserTag + + getUserTagAncestors(id: ID!): [UserTag] + getAllNotesForAgendaItem(agendaItemId: ID!): [Note] advertisementsConnection( diff --git a/src/typeDefs/types.ts b/src/typeDefs/types.ts index a3a1b41ffa..8faf1a6521 100644 --- a/src/typeDefs/types.ts +++ b/src/typeDefs/types.ts @@ -75,6 +75,7 @@ export const types = gql` actionItemCategory: ActionItemCategory preCompletionNotes: String postCompletionNotes: String + allotedHours: Float assignmentDate: Date! dueDate: Date! completionDate: Date! @@ -341,6 +342,7 @@ export const types = gql` type FundraisingCampaign { _id: ID! fundId: Fund! + organizationId: Organization! name: String! startDate: Date! endDate: Date! @@ -352,7 +354,7 @@ export const types = gql` } type FundraisingCampaignPledge { _id: ID! - campaigns: [FundraisingCampaign]! + campaign: FundraisingCampaign! users: [User]! startDate: Date endDate: Date @@ -588,7 +590,7 @@ export const types = gql` type SocialMediaUrls { facebook: String instagram: String - twitter: String + X: String linkedIn: String gitHub: String youTube: String @@ -622,6 +624,7 @@ export const types = gql` type User { _id: ID! + identifier: Int! appUserProfileId: AppUserProfile address: Address birthDate: Date @@ -663,6 +666,8 @@ export const types = gql` createdEvents: [Event] createdOrganizations: [Organization] eventAdmin: [Event] + pledges: [FundraisingCampaignPledge] + campaigns: [FundraisingCampaign] pluginCreationAllowed: Boolean! isSuperAdmin: Boolean! appLanguageCode: String! @@ -739,7 +744,7 @@ export const types = gql` type UserTagsConnection { edges: [UserTagsConnectionEdge!]! pageInfo: DefaultConnectionPageInfo! - totalCount: PositiveInt + totalCount: Int } """ @@ -756,6 +761,7 @@ export const types = gql` type UsersConnection { edges: [UsersConnectionEdge!]! pageInfo: DefaultConnectionPageInfo! + totalCount: Int } """ diff --git a/src/types/generatedGraphQLTypes.ts b/src/types/generatedGraphQLTypes.ts index 4c05807c81..0f4178071b 100644 --- a/src/types/generatedGraphQLTypes.ts +++ b/src/types/generatedGraphQLTypes.ts @@ -72,6 +72,7 @@ export type ActionItem = { __typename?: 'ActionItem'; _id: Scalars['ID']['output']; actionItemCategory?: Maybe; + allotedHours?: Maybe; assignee?: Maybe; assigner?: Maybe; assignmentDate: Scalars['Date']['output']; @@ -97,16 +98,24 @@ export type ActionItemCategory = { updatedAt: Scalars['Date']['output']; }; +export type ActionItemCategoryWhereInput = { + is_disabled?: InputMaybe; + name_contains?: InputMaybe; +}; + export type ActionItemWhereInput = { actionItemCategory_id?: InputMaybe; + assigneeName?: InputMaybe; + categoryName?: InputMaybe; event_id?: InputMaybe; - is_active?: InputMaybe; is_completed?: InputMaybe; }; export type ActionItemsOrderByInput = | 'createdAt_ASC' - | 'createdAt_DESC'; + | 'createdAt_DESC' + | 'dueDate_ASC' + | 'dueDate_DESC'; export type Address = { __typename?: 'Address'; @@ -222,10 +231,12 @@ export type AppUserProfile = { _id: Scalars['ID']['output']; adminFor?: Maybe>>; appLanguageCode: Scalars['String']['output']; + campaigns?: Maybe>>; createdEvents?: Maybe>>; createdOrganizations?: Maybe>>; eventAdmin?: Maybe>>; isSuperAdmin: Scalars['Boolean']['output']; + pledges?: Maybe>>; pluginCreationAllowed: Scalars['Boolean']['output']; userId: User; }; @@ -247,7 +258,10 @@ export type CampaignOrderByInput = | 'startDate_DESC'; export type CampaignWhereInput = { + fundId?: InputMaybe; + id?: InputMaybe; name_contains?: InputMaybe; + organizationId?: InputMaybe; }; export type CheckIn = { @@ -338,6 +352,7 @@ export type ConnectionPageInfo = { }; export type CreateActionItemInput = { + allotedHours?: InputMaybe; assigneeId: Scalars['ID']['input']; dueDate?: InputMaybe; eventId?: InputMaybe; @@ -826,6 +841,12 @@ export type EventVolunteerGroupInput = { volunteersRequired?: InputMaybe; }; +export type EventVolunteerGroupWhereInput = { + eventId?: InputMaybe; + name_contains?: InputMaybe; + volunteerId?: InputMaybe; +}; + export type EventVolunteerInput = { eventId: Scalars['ID']['input']; groupId: Scalars['ID']['input']; @@ -924,6 +945,7 @@ export type FundCampaignInput = { fundId: Scalars['ID']['input']; fundingGoal: Scalars['Float']['input']; name: Scalars['String']['input']; + organizationId: Scalars['ID']['input']; startDate: Scalars['Date']['input']; }; @@ -962,6 +984,7 @@ export type FundraisingCampaign = { fundId: Fund; fundingGoal: Scalars['Float']['output']; name: Scalars['String']['output']; + organizationId: Organization; pledges?: Maybe>>; startDate: Scalars['Date']['output']; updatedAt: Scalars['DateTime']['output']; @@ -971,7 +994,7 @@ export type FundraisingCampaignPledge = { __typename?: 'FundraisingCampaignPledge'; _id: Scalars['ID']['output']; amount: Scalars['Float']['output']; - campaigns: Array>; + campaign: FundraisingCampaign; currency: Currency; endDate?: Maybe; startDate?: Maybe; @@ -1215,8 +1238,6 @@ export type Mutation = { removeEventAttendee: User; removeEventVolunteer: EventVolunteer; removeEventVolunteerGroup: EventVolunteerGroup; - removeFund: Fund; - removeFundraisingCampaign: FundraisingCampaign; removeFundraisingCampaignPledge: FundraisingCampaignPledge; removeGroupChat: GroupChat; removeMember: Organization; @@ -1376,6 +1397,7 @@ export type MutationCreateActionItemArgs = { export type MutationCreateActionItemCategoryArgs = { + isDisabled: Scalars['Boolean']['input']; name: Scalars['String']['input']; organizationId: Scalars['ID']['input']; }; @@ -1665,16 +1687,6 @@ export type MutationRemoveEventVolunteerGroupArgs = { }; -export type MutationRemoveFundArgs = { - id: Scalars['ID']['input']; -}; - - -export type MutationRemoveFundraisingCampaignArgs = { - id: Scalars['ID']['input']; -}; - - export type MutationRemoveFundraisingCampaignPledgeArgs = { id: Scalars['ID']['input']; }; @@ -2124,6 +2136,13 @@ export type PledgeOrderByInput = | 'startDate_ASC' | 'startDate_DESC'; +export type PledgeWhereInput = { + campaignId?: InputMaybe; + firstName_contains?: InputMaybe; + id?: InputMaybe; + name_contains?: InputMaybe; +}; + export type Plugin = { __typename?: 'Plugin'; _id: Scalars['ID']['output']; @@ -2279,11 +2298,15 @@ export type Query = { getEventAttendee?: Maybe; getEventAttendeesByEventId?: Maybe>>; getEventInvitesByUserId: Array; + getEventVolunteerGroups: Array>; getFundById: Fund; - getFundraisingCampaignById: FundraisingCampaign; getFundraisingCampaignPledgeById: FundraisingCampaignPledge; + getFundraisingCampaigns: Array>; getNoteById: Note; + getPledgesByUserId?: Maybe>>; getPlugins?: Maybe>>; + getUserTag?: Maybe; + getUserTagAncestors?: Maybe>>; getVenueByOrgId?: Maybe>>; getlanguage?: Maybe>>; groupChatById?: Maybe; @@ -2309,7 +2332,9 @@ export type Query = { export type QueryActionItemCategoriesByOrganizationArgs = { + orderBy?: InputMaybe; organizationId: Scalars['ID']['input']; + where?: InputMaybe; }; @@ -2319,6 +2344,7 @@ export type QueryActionItemsByEventArgs = { export type QueryActionItemsByOrganizationArgs = { + eventId?: InputMaybe; orderBy?: InputMaybe; organizationId: Scalars['ID']['input']; where?: InputMaybe; @@ -2463,6 +2489,11 @@ export type QueryGetEventInvitesByUserIdArgs = { }; +export type QueryGetEventVolunteerGroupsArgs = { + where?: InputMaybe; +}; + + export type QueryGetFundByIdArgs = { id: Scalars['ID']['input']; orderBy?: InputMaybe; @@ -2470,18 +2501,36 @@ export type QueryGetFundByIdArgs = { }; -export type QueryGetFundraisingCampaignByIdArgs = { +export type QueryGetFundraisingCampaignPledgeByIdArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryGetFundraisingCampaignsArgs = { + campaignOrderby?: InputMaybe; + pledgeOrderBy?: InputMaybe; + where?: InputMaybe; +}; + + +export type QueryGetNoteByIdArgs = { id: Scalars['ID']['input']; +}; + + +export type QueryGetPledgesByUserIdArgs = { orderBy?: InputMaybe; + userId: Scalars['ID']['input']; + where?: InputMaybe; }; -export type QueryGetFundraisingCampaignPledgeByIdArgs = { +export type QueryGetUserTagArgs = { id: Scalars['ID']['input']; }; -export type QueryGetNoteByIdArgs = { +export type QueryGetUserTagAncestorsArgs = { id: Scalars['ID']['input']; }; @@ -2639,24 +2688,24 @@ export type RecurringEventMutationType = export type SocialMediaUrls = { __typename?: 'SocialMediaUrls'; + X?: Maybe; facebook?: Maybe; gitHub?: Maybe; instagram?: Maybe; linkedIn?: Maybe; reddit?: Maybe; slack?: Maybe; - twitter?: Maybe; youTube?: Maybe; }; export type SocialMediaUrlsInput = { + X?: InputMaybe; facebook?: InputMaybe; gitHub?: InputMaybe; instagram?: InputMaybe; linkedIn?: InputMaybe; reddit?: InputMaybe; slack?: InputMaybe; - twitter?: InputMaybe; youTube?: InputMaybe; }; @@ -2732,6 +2781,7 @@ export type UpdateActionItemCategoryInput = { }; export type UpdateActionItemInput = { + allotedHours?: InputMaybe; assigneeId?: InputMaybe; completionDate?: InputMaybe; dueDate?: InputMaybe; @@ -2873,9 +2923,9 @@ export type UpdateUserPasswordInput = { }; export type UpdateUserTagInput = { - _id: Scalars['ID']['input']; name: Scalars['String']['input']; - tagColor: Scalars['String']['input']; + tagColor?: InputMaybe; + tagId: Scalars['ID']['input']; }; export type User = { @@ -2891,6 +2941,7 @@ export type User = { eventAdmin?: Maybe>>; firstName: Scalars['String']['output']; gender?: Maybe; + identifier: Scalars['Int']['output']; image?: Maybe; joinedOrganizations?: Maybe>>; lastName: Scalars['String']['output']; @@ -3049,7 +3100,7 @@ export type UserTagsConnection = { __typename?: 'UserTagsConnection'; edges: Array; pageInfo: DefaultConnectionPageInfo; - totalCount?: Maybe; + totalCount?: Maybe; }; /** A default connection edge on the UserTag type for UserTagsConnection. */ @@ -3098,6 +3149,7 @@ export type UsersConnection = { __typename?: 'UsersConnection'; edges: Array; pageInfo: DefaultConnectionPageInfo; + totalCount?: Maybe; }; /** A default connection edge on the User type for UsersConnection. */ @@ -3250,6 +3302,7 @@ export type ResolversInterfaceTypes<_RefType extends Record> = export type ResolversTypes = { ActionItem: ResolverTypeWrapper; ActionItemCategory: ResolverTypeWrapper; + ActionItemCategoryWhereInput: ActionItemCategoryWhereInput; ActionItemWhereInput: ActionItemWhereInput; ActionItemsOrderByInput: ActionItemsOrderByInput; Address: ResolverTypeWrapper
; @@ -3317,6 +3370,7 @@ export type ResolversTypes = { EventVolunteer: ResolverTypeWrapper; EventVolunteerGroup: ResolverTypeWrapper; EventVolunteerGroupInput: EventVolunteerGroupInput; + EventVolunteerGroupWhereInput: EventVolunteerGroupWhereInput; EventVolunteerInput: EventVolunteerInput; EventVolunteerResponse: EventVolunteerResponse; EventWhereInput: EventWhereInput; @@ -3378,6 +3432,7 @@ export type ResolversTypes = { PaginationDirection: PaginationDirection; PhoneNumber: ResolverTypeWrapper; PledgeOrderByInput: PledgeOrderByInput; + PledgeWhereInput: PledgeWhereInput; Plugin: ResolverTypeWrapper; PluginField: ResolverTypeWrapper; PluginFieldInput: PluginFieldInput; @@ -3464,6 +3519,7 @@ export type ResolversTypes = { export type ResolversParentTypes = { ActionItem: InterfaceActionItemModel; ActionItemCategory: InterfaceActionItemCategoryModel; + ActionItemCategoryWhereInput: ActionItemCategoryWhereInput; ActionItemWhereInput: ActionItemWhereInput; Address: Address; AddressInput: AddressInput; @@ -3524,6 +3580,7 @@ export type ResolversParentTypes = { EventVolunteer: InterfaceEventVolunteerModel; EventVolunteerGroup: InterfaceEventVolunteerGroupModel; EventVolunteerGroupInput: EventVolunteerGroupInput; + EventVolunteerGroupWhereInput: EventVolunteerGroupWhereInput; EventVolunteerInput: EventVolunteerInput; EventWhereInput: EventWhereInput; ExtendSession: ExtendSession; @@ -3576,6 +3633,7 @@ export type ResolversParentTypes = { OtpData: OtpData; PageInfo: PageInfo; PhoneNumber: Scalars['PhoneNumber']['output']; + PledgeWhereInput: PledgeWhereInput; Plugin: InterfacePluginModel; PluginField: InterfacePluginFieldModel; PluginFieldInput: PluginFieldInput; @@ -3662,6 +3720,7 @@ export type RoleDirectiveResolver = { _id?: Resolver; actionItemCategory?: Resolver, ParentType, ContextType>; + allotedHours?: Resolver, ParentType, ContextType>; assignee?: Resolver, ParentType, ContextType>; assigner?: Resolver, ParentType, ContextType>; assignmentDate?: Resolver; @@ -3789,10 +3848,12 @@ export type AppUserProfileResolvers; adminFor?: Resolver>>, ParentType, ContextType>; appLanguageCode?: Resolver; + campaigns?: Resolver>>, ParentType, ContextType>; createdEvents?: Resolver>>, ParentType, ContextType>; createdOrganizations?: Resolver>>, ParentType, ContextType>; eventAdmin?: Resolver>>, ParentType, ContextType>; isSuperAdmin?: Resolver; + pledges?: Resolver>>, ParentType, ContextType>; pluginCreationAllowed?: Resolver; userId?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -4099,6 +4160,7 @@ export type FundraisingCampaignResolvers; fundingGoal?: Resolver; name?: Resolver; + organizationId?: Resolver; pledges?: Resolver>>, ParentType, ContextType>; startDate?: Resolver; updatedAt?: Resolver; @@ -4108,7 +4170,7 @@ export type FundraisingCampaignResolvers = { _id?: Resolver; amount?: Resolver; - campaigns?: Resolver>, ParentType, ContextType>; + campaign?: Resolver; currency?: Resolver; endDate?: Resolver, ParentType, ContextType>; startDate?: Resolver, ParentType, ContextType>; @@ -4264,7 +4326,7 @@ export type MutationResolvers>; checkOut?: Resolver>; createActionItem?: Resolver>; - createActionItemCategory?: Resolver>; + createActionItemCategory?: Resolver>; createAdmin?: Resolver>; createAdvertisement?: Resolver, ParentType, ContextType, RequireFields>; createAgendaCategory?: Resolver>; @@ -4321,8 +4383,6 @@ export type MutationResolvers>; removeEventVolunteer?: Resolver>; removeEventVolunteerGroup?: Resolver>; - removeFund?: Resolver>; - removeFundraisingCampaign?: Resolver>; removeFundraisingCampaignPledge?: Resolver>; removeGroupChat?: Resolver>; removeMember?: Resolver>; @@ -4551,11 +4611,15 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; getEventAttendeesByEventId?: Resolver>>, ParentType, ContextType, RequireFields>; getEventInvitesByUserId?: Resolver, ParentType, ContextType, RequireFields>; + getEventVolunteerGroups?: Resolver>, ParentType, ContextType, Partial>; getFundById?: Resolver>; - getFundraisingCampaignById?: Resolver>; getFundraisingCampaignPledgeById?: Resolver>; + getFundraisingCampaigns?: Resolver>, ParentType, ContextType, Partial>; getNoteById?: Resolver>; + getPledgesByUserId?: Resolver>>, ParentType, ContextType, RequireFields>; getPlugins?: Resolver>>, ParentType, ContextType>; + getUserTag?: Resolver, ParentType, ContextType, RequireFields>; + getUserTagAncestors?: Resolver>>, ParentType, ContextType, RequireFields>; getVenueByOrgId?: Resolver>>, ParentType, ContextType, RequireFields>; getlanguage?: Resolver>>, ParentType, ContextType, RequireFields>; groupChatById?: Resolver, ParentType, ContextType, RequireFields>; @@ -4595,13 +4659,13 @@ export type RecurrenceRuleResolvers = { + X?: Resolver, ParentType, ContextType>; facebook?: Resolver, ParentType, ContextType>; gitHub?: Resolver, ParentType, ContextType>; instagram?: Resolver, ParentType, ContextType>; linkedIn?: Resolver, ParentType, ContextType>; reddit?: Resolver, ParentType, ContextType>; slack?: Resolver, ParentType, ContextType>; - twitter?: Resolver, ParentType, ContextType>; youTube?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -4660,6 +4724,7 @@ export type UserResolvers>>, ParentType, ContextType>; firstName?: Resolver; gender?: Resolver, ParentType, ContextType>; + identifier?: Resolver; image?: Resolver, ParentType, ContextType>; joinedOrganizations?: Resolver>>, ParentType, ContextType>; lastName?: Resolver; @@ -4740,7 +4805,7 @@ export type UserTagResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; - totalCount?: Resolver, ParentType, ContextType>; + totalCount?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -4753,6 +4818,7 @@ export type UserTagsConnectionEdgeResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; + totalCount?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/src/utilities/loadSampleData.ts b/src/utilities/loadSampleData.ts index 6309770018..7ff7e666da 100644 --- a/src/utilities/loadSampleData.ts +++ b/src/utilities/loadSampleData.ts @@ -4,6 +4,7 @@ import yargs from "yargs"; import { connect } from "../db"; import { ActionItemCategory, + AgendaCategoryModel, AppUserProfile, Community, Event, @@ -63,6 +64,7 @@ async function formatDatabase(): Promise { User.deleteMany({}), Organization.deleteMany({}), ActionItemCategory.deleteMany({}), + AgendaCategoryModel.deleteMany({}), Event.deleteMany({}), Post.deleteMany({}), AppUserProfile.deleteMany({}), @@ -120,6 +122,9 @@ async function insertCollections(collections: string[]): Promise { case "actionItemCategories": await ActionItemCategory.insertMany(docs); break; + case "agendaCategories": + await AgendaCategoryModel.insertMany(docs); + break; case "events": await Event.insertMany(docs); break; @@ -163,6 +168,7 @@ async function checkCountAfterImport(): Promise { { name: "users", model: User }, { name: "organizations", model: Organization }, { name: "actionItemCategories", model: ActionItemCategory }, + { name: "agendaCategories", model: AgendaCategoryModel }, { name: "events", model: Event }, { name: "recurrenceRules", model: RecurrenceRule }, { name: "posts", model: Post }, @@ -198,6 +204,7 @@ const collections = [ "recurrenceRules", "appUserProfiles", "actionItemCategories", + "agendaCategories", ]; // Check if specific collections need to be inserted diff --git a/tests/helpers/FundraisingCampaign.ts b/tests/helpers/FundraisingCampaign.ts index 34b517c9df..60ead10016 100644 --- a/tests/helpers/FundraisingCampaign.ts +++ b/tests/helpers/FundraisingCampaign.ts @@ -4,6 +4,7 @@ import { FundraisingCampaign, type InterfaceFundraisingCampaign, } from "../../src/models"; +import { Types } from "mongoose"; export type TestFundCampaignType = | (InterfaceFundraisingCampaign & Document) @@ -11,16 +12,20 @@ export type TestFundCampaignType = export const createTestFundraisingCampaign = async ( fundId: string, + organizationId?: Types.ObjectId | undefined, + populated?: string[], ): Promise => { // const [testUser, testOrganization, testFund] = await createTestFund(); const testFundraisingCampaign = await FundraisingCampaign.create({ name: `name${nanoid().toLowerCase()}`, fundId: fundId, + organizationId: organizationId ?? new Types.ObjectId(), startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", fundingGoal: 1000, + pledges: [], }); await Fund.updateOne( { @@ -33,5 +38,9 @@ export const createTestFundraisingCampaign = async ( }, ); - return testFundraisingCampaign; + const finalFundraisingCampaign = await FundraisingCampaign.findOne({ + _id: testFundraisingCampaign._id, + }).populate(populated || []); + + return finalFundraisingCampaign as InterfaceFundraisingCampaign; }; diff --git a/tests/helpers/FundraisingCampaignPledge.ts b/tests/helpers/FundraisingCampaignPledge.ts index 7d052032ff..7a8d3cac39 100644 --- a/tests/helpers/FundraisingCampaignPledge.ts +++ b/tests/helpers/FundraisingCampaignPledge.ts @@ -1,4 +1,5 @@ import { + AppUserProfile, FundraisingCampaign, type InterfaceFundraisingCampaign, } from "../../src/models"; @@ -28,11 +29,12 @@ export const createTestFundraisingCampaignPledge = async (): Promise< const testFund = userOrgAndFund[2]; let testFundraisingCampaign = await createTestFundraisingCampaign( testFund?._id, + testOrganization?._id, ); if (testUser && testOrganization && testFund && testFundraisingCampaign) { const testFundraisingCampaignPledge = (await FundraisingCampaignPledge.create({ - campaigns: [testFundraisingCampaign._id], + campaign: testFundraisingCampaign._id, users: [testUser._id.toString()], startDate: new Date(), endDate: new Date(), @@ -52,6 +54,19 @@ export const createTestFundraisingCampaignPledge = async (): Promise< new: true, }, )) as InterfaceFundraisingCampaign; + + await AppUserProfile.updateOne( + { + userId: testUser._id, + }, + { + $addToSet: { + pledges: testFundraisingCampaignPledge?._id, + campaigns: testFundraisingCampaign._id, + }, + }, + ); + return [ testUser, testOrganization, diff --git a/tests/helpers/actionItem.ts b/tests/helpers/actionItem.ts index 6fe3c3a395..86eab57629 100644 --- a/tests/helpers/actionItem.ts +++ b/tests/helpers/actionItem.ts @@ -33,10 +33,11 @@ export const createTestActionItem = async (): Promise< }); const testActionItem = await ActionItem.create({ - creatorId: testUser?._id, - assigneeId: randomUser?._id, - assignerId: testUser?._id, - actionItemCategoryId: testCategory?._id, + creator: testUser?._id, + assignee: randomUser?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + organization: testOrganization?._id, }); return [testUser, testOrganization, testCategory, testActionItem, randomUser]; @@ -46,25 +47,28 @@ interface InterfaceCreateNewTestAction { currUserId: string; assignedUserId: string; actionItemCategoryId: string; + organizationId: string; } export const createNewTestActionItem = async ({ currUserId, assignedUserId, actionItemCategoryId, + organizationId, }: InterfaceCreateNewTestAction): Promise => { const newTestActionItem = await ActionItem.create({ - creatorId: currUserId, - assigneeId: assignedUserId, - assignerId: currUserId, - actionItemCategoryId: actionItemCategoryId, + creator: currUserId, + assignee: assignedUserId, + assigner: currUserId, + actionItemCategory: actionItemCategoryId, + organization: organizationId, }); return newTestActionItem; }; export const createTestActionItems = async (): Promise< - [TestUserType, TestEventType, TestOrganizationType] + [TestUserType, TestUserType, TestEventType, TestOrganizationType] > => { const randomUser = await createTestUser(); const [testUser, testOrganization, testCategory] = await createTestCategory(); @@ -76,26 +80,29 @@ export const createTestActionItems = async (): Promise< }); const testActionItem1 = await ActionItem.create({ - creatorId: testUser?._id, - assigneeId: randomUser?._id, - assignerId: testUser?._id, - actionItemCategoryId: testCategory?._id, + creator: testUser?._id, + assignee: randomUser?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + organization: testOrganization?._id, isCompleted: true, }); const testActionItem2 = await ActionItem.create({ - creatorId: testUser?._id, - assigneeId: randomUser?._id, - assignerId: testUser?._id, - actionItemCategoryId: testCategory?._id, + creator: testUser?._id, + assignee: randomUser?._id, + assigner: testUser?._id, + actionItemCategory: testCategory?._id, + organization: testOrganization?._id, isCompleted: false, }); await ActionItem.create({ - creatorId: testUser?._id, - assigneeId: randomUser?._id, - assignerId: testUser?._id, - actionItemCategoryId: testCategory2?._id, + creator: testUser?._id, + assignee: randomUser?._id, + assigner: testUser?._id, + actionItemCategory: testCategory2?._id, + organization: testOrganization?._id, isCompleted: true, }); @@ -118,7 +125,7 @@ export const createTestActionItems = async (): Promise< _id: testActionItem1?._id, }, { - eventId: testEvent?._id, + event: testEvent?._id, }, ); @@ -127,9 +134,9 @@ export const createTestActionItems = async (): Promise< _id: testActionItem2?._id, }, { - eventId: testEvent?._id, + event: testEvent?._id, }, ); - return [testUser, testEvent, testOrganization]; + return [testUser, randomUser, testEvent, testOrganization]; }; diff --git a/tests/helpers/directChat.ts b/tests/helpers/directChat.ts index 0b4d523b23..96d8888e78 100644 --- a/tests/helpers/directChat.ts +++ b/tests/helpers/directChat.ts @@ -9,7 +9,7 @@ import { createTestUserAndOrganization } from "./userAndOrg"; import type { Document } from "mongoose"; export type TestDirectChatType = - | (InterfaceDirectChat & Document) + | (InterfaceDirectChat & Document) | null; export type TestDirectChatMessageType = diff --git a/tests/helpers/events.ts b/tests/helpers/events.ts index 89c3391244..75298ee74a 100644 --- a/tests/helpers/events.ts +++ b/tests/helpers/events.ts @@ -1,12 +1,17 @@ import type { Document } from "mongoose"; import { nanoid } from "nanoid"; import { EventVolunteerResponse } from "../../src/constants"; -import type { InterfaceEvent, InterfaceEventVolunteer } from "../../src/models"; +import type { + InterfaceEvent, + InterfaceEventVolunteer, + InterfaceEventVolunteerGroup, +} from "../../src/models"; import { AppUserProfile, Event, EventAttendee, EventVolunteer, + EventVolunteerGroup, User, } from "../../src/models"; import type { TestOrganizationType, TestUserType } from "./userAndOrg"; @@ -18,6 +23,9 @@ export type TestEventVolunteerType = | (InterfaceEventVolunteer & Document) | null; +export type TestEventVolunteerGroupType = InterfaceEventVolunteerGroup & + Document; + export const createTestEvent = async (): Promise< [TestUserType, TestOrganizationType, TestEventType] > => { @@ -134,3 +142,32 @@ export const createTestEventAndVolunteer = async (): Promise< return [volunteerUser, creatorUser, testEvent, testEventVolunteer]; }; + +export const createTestEventVolunteerGroup = async (): Promise< + [ + TestUserType, + TestUserType, + TestEventType, + TestEventVolunteerType, + TestEventVolunteerGroupType, + ] +> => { + const [creatorUser, volunteerUser, testEvent, testEventVolunteer] = + await createTestEventAndVolunteer(); + const testEventVolunteerGroup = await EventVolunteerGroup.create({ + name: "testEventVolunteerGroup", + volunteersRequired: 1, + eventId: testEvent?._id, + creatorId: creatorUser?._id, + leaderId: creatorUser?._id, + volunteers: [testEventVolunteer?._id], + }); + + return [ + creatorUser, + volunteerUser, + testEvent, + testEventVolunteer, + testEventVolunteerGroup, + ]; +}; diff --git a/tests/helpers/groupChat.ts b/tests/helpers/groupChat.ts index a41591c962..a7a53b96b9 100644 --- a/tests/helpers/groupChat.ts +++ b/tests/helpers/groupChat.ts @@ -9,7 +9,7 @@ import { createTestUserAndOrganization } from "./userAndOrg"; import type { Document } from "mongoose"; export type TestGroupChatType = - | (InterfaceGroupChat & Document) + | (InterfaceGroupChat & Document) | null; export type TestGroupChatMessageType = diff --git a/tests/helpers/userAndOrg.ts b/tests/helpers/userAndOrg.ts index 26fc174320..d7dde673dd 100644 --- a/tests/helpers/userAndOrg.ts +++ b/tests/helpers/userAndOrg.ts @@ -29,6 +29,8 @@ export const createTestUser = async (): Promise => { const testUserAppProfile = await AppUserProfile.create({ userId: testUser._id, appLanguageCode: "en", + pledges: [], + campaigns: [], }); testUser = (await User.findOneAndUpdate( { diff --git a/tests/libraries/errors/inputValidationError.spec.ts b/tests/libraries/errors/inputValidationError.spec.ts new file mode 100644 index 0000000000..862ef13877 --- /dev/null +++ b/tests/libraries/errors/inputValidationError.spec.ts @@ -0,0 +1,26 @@ +import "dotenv/config"; +import { describe, it, expect } from "vitest"; +import { USER_FAMILY_MIN_MEMBERS_ERROR_CODE } from "../../../src/constants"; +import { errors } from "../../../src/libraries"; + +describe("libraries -> errors -> InputValidationError", () => { + it(`should throw InputValidationError error when input is invalid`, () => { + try { + throw new errors.InputValidationError( + USER_FAMILY_MIN_MEMBERS_ERROR_CODE.MESSAGE, + USER_FAMILY_MIN_MEMBERS_ERROR_CODE.CODE, + USER_FAMILY_MIN_MEMBERS_ERROR_CODE.PARAM, + ); + } catch (error: unknown) { + if (error instanceof errors.InputValidationError) { + expect(error.errors).toEqual([ + expect.objectContaining({ + message: USER_FAMILY_MIN_MEMBERS_ERROR_CODE.MESSAGE, + code: USER_FAMILY_MIN_MEMBERS_ERROR_CODE.CODE, + param: USER_FAMILY_MIN_MEMBERS_ERROR_CODE.PARAM, + }), + ]); + } + } + }); +}); diff --git a/tests/libraries/errors/invalidFileTypeError.spec.ts b/tests/libraries/errors/invalidFileTypeError.spec.ts new file mode 100644 index 0000000000..15fe730eb3 --- /dev/null +++ b/tests/libraries/errors/invalidFileTypeError.spec.ts @@ -0,0 +1,26 @@ +import "dotenv/config"; +import { describe, it, expect } from "vitest"; +import { INVALID_FILE_TYPE } from "../../../src/constants"; +import { errors } from "../../../src/libraries"; + +describe("libraries -> errors -> InvalidFileTypeError", () => { + it(`should throw InvalidFileTypeError error when invalid file type is received`, () => { + try { + throw new errors.InvalidFileTypeError( + INVALID_FILE_TYPE.MESSAGE, + INVALID_FILE_TYPE.CODE, + INVALID_FILE_TYPE.PARAM, + ); + } catch (error: unknown) { + if (error instanceof errors.InvalidFileTypeError) { + expect(error.errors).toEqual([ + expect.objectContaining({ + message: INVALID_FILE_TYPE.MESSAGE, + code: INVALID_FILE_TYPE.CODE, + param: INVALID_FILE_TYPE.PARAM, + }), + ]); + } + } + }); +}); diff --git a/tests/resolvers/ActionItem/assignee.spec.ts b/tests/resolvers/ActionItem/assignee.spec.ts deleted file mode 100644 index d910b54919..0000000000 --- a/tests/resolvers/ActionItem/assignee.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import "dotenv/config"; -import { assignee as assigneeResolver } from "../../../src/resolvers/ActionItem/assignee"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { User } from "../../../src/models"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import type { TestActionItemType } from "../../helpers/actionItem"; -import { createTestActionItem } from "../../helpers/actionItem"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let randomTestUser: TestUserType; -let testActionItem: TestActionItemType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [, , , testActionItem, randomTestUser] = await createTestActionItem(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> ActionItem -> assignee", () => { - it(`returns the assignee for parent action item`, async () => { - const parent = testActionItem?.toObject(); - - const assignedToPayload = await assigneeResolver?.(parent, {}, {}); - - const assignedToObject = await User.findOne({ - _id: randomTestUser?._id, - }).lean(); - - expect(assignedToPayload).toEqual(assignedToObject); - }); -}); diff --git a/tests/resolvers/ActionItem/assigner.spec.ts b/tests/resolvers/ActionItem/assigner.spec.ts deleted file mode 100644 index e27fee41cb..0000000000 --- a/tests/resolvers/ActionItem/assigner.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import "dotenv/config"; -import { assigner as assignerResolver } from "../../../src/resolvers/ActionItem/assigner"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { User } from "../../../src/models"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import type { TestActionItemType } from "../../helpers/actionItem"; -import { createTestActionItem } from "../../helpers/actionItem"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testActionItem: TestActionItemType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [testUser, , , testActionItem] = await createTestActionItem(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> ActionItem -> assigner", () => { - it(`returns the assigner for parent action item`, async () => { - const parent = testActionItem?.toObject(); - - const assignedByPayload = await assignerResolver?.(parent, {}, {}); - - const assignedByObject = await User.findOne({ - _id: testUser?._id, - }).lean(); - - expect(assignedByPayload).toEqual(assignedByObject); - }); -}); diff --git a/tests/resolvers/ActionItem/category.spec.ts b/tests/resolvers/ActionItem/category.spec.ts deleted file mode 100644 index e7b87bdb94..0000000000 --- a/tests/resolvers/ActionItem/category.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import "dotenv/config"; -import { actionItemCategory as actionItemCategoryResolver } from "../../../src/resolvers/ActionItem/actionItemCategory"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { ActionItemCategory } from "../../../src/models"; -import type { TestActionItemType } from "../../helpers/actionItem"; -import { createTestActionItem } from "../../helpers/actionItem"; -import type { TestActionItemCategoryType } from "../../helpers/actionItemCategory"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testActionItem: TestActionItemType; -let testCategory: TestActionItemCategoryType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [, , testCategory, testActionItem] = await createTestActionItem(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> ActionItem -> actionItemCategory", () => { - it(`returns the actionItemCategory for parent action item`, async () => { - const parent = testActionItem?.toObject(); - - const actionItemCategoryPayload = await actionItemCategoryResolver?.( - parent, - {}, - {}, - ); - - const actionItemCategoryObject = await ActionItemCategory.findOne({ - _id: testCategory?._id, - }).lean(); - - expect(actionItemCategoryPayload).toEqual(actionItemCategoryObject); - }); -}); diff --git a/tests/resolvers/ActionItem/creator.spec.ts b/tests/resolvers/ActionItem/creator.spec.ts deleted file mode 100644 index 91a19580bf..0000000000 --- a/tests/resolvers/ActionItem/creator.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import "dotenv/config"; -import { creator as creatorResolver } from "../../../src/resolvers/ActionItem/creator"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { User } from "../../../src/models"; -import type { TestUserType } from "../../helpers/userAndOrg"; -import type { TestActionItemType } from "../../helpers/actionItem"; -import { createTestActionItem } from "../../helpers/actionItem"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testActionItem: TestActionItemType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [testUser, , , testActionItem] = await createTestActionItem(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> ActionItem -> creator", () => { - it(`returns the creator for parent action item`, async () => { - const parent = testActionItem?.toObject(); - - const createdByPayload = await creatorResolver?.(parent, {}, {}); - - const createdByObject = await User.findOne({ - _id: testUser?._id, - }).lean(); - - expect(createdByPayload).toEqual(createdByObject); - }); -}); diff --git a/tests/resolvers/ActionItem/event.spec.ts b/tests/resolvers/ActionItem/event.spec.ts deleted file mode 100644 index 1dabeb7508..0000000000 --- a/tests/resolvers/ActionItem/event.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import "dotenv/config"; -import { event as eventResolver } from "../../../src/resolvers/ActionItem/event"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import type { InterfaceActionItem } from "../../../src/models"; -import { ActionItem, Event } from "../../../src/models"; -import type { - TestOrganizationType, - TestUserType, -} from "../../helpers/userAndOrg"; -import type { TestActionItemType } from "../../helpers/actionItem"; -import { createTestActionItem } from "../../helpers/actionItem"; -import { nanoid } from "nanoid"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testOrganization: TestOrganizationType; -let testActionItem: TestActionItemType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [testUser, testOrganization, , testActionItem] = await createTestActionItem(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> ActionItem -> event", () => { - it(`returns the event for parent action item`, async () => { - const testEvent = await Event.create({ - title: `title${nanoid().toLowerCase()}`, - description: `description${nanoid().toLowerCase()}`, - allDay: true, - startDate: new Date(), - recurring: true, - isPublic: true, - isRegisterable: true, - creatorId: testUser?._id, - admins: [testUser?._id], - organization: testOrganization?._id, - }); - - const updatedTestActionItem = await ActionItem.findOneAndUpdate( - { - _id: testActionItem?._id, - }, - { - eventId: testEvent?._id, - }, - { - new: true, - }, - ); - - const parent = updatedTestActionItem?.toObject(); - - const eventByPayload = await eventResolver?.( - parent as InterfaceActionItem, - {}, - {}, - ); - - expect(eventByPayload?._id).toEqual(updatedTestActionItem?.eventId); - }); -}); diff --git a/tests/resolvers/ActionItemCategory/organization.spec.ts b/tests/resolvers/ActionItemCategory/organization.spec.ts deleted file mode 100644 index 2c1a06e0d2..0000000000 --- a/tests/resolvers/ActionItemCategory/organization.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import "dotenv/config"; -import { organization as organizationResolver } from "../../../src/resolvers/ActionItemCategory/organization"; -import { connect, disconnect } from "../../helpers/db"; -import type mongoose from "mongoose"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; -import { Organization } from "../../../src/models"; -import { type TestOrganizationType } from "../../helpers/userAndOrg"; -import type { TestActionItemCategoryType } from "../../helpers/actionItemCategory"; -import { createTestCategory } from "../../helpers/actionItemCategory"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testOrganization: TestOrganizationType; -let testCategory: TestActionItemCategoryType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - [, testOrganization, testCategory] = await createTestCategory(); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers -> ActionItemCategory -> org", () => { - it(`returns the organization object for parent actionItemCategory`, async () => { - const parent = testCategory?.toObject(); - - const orgPayload = await organizationResolver?.(parent, {}, {}); - - const orgObject = await Organization.findOne({ - _id: testOrganization?._id, - }).lean(); - - expect(orgPayload).toEqual(orgObject); - }); -}); diff --git a/tests/resolvers/Event/actionItems.spec.ts b/tests/resolvers/Event/actionItems.spec.ts index a984e00f05..716f511d96 100644 --- a/tests/resolvers/Event/actionItems.spec.ts +++ b/tests/resolvers/Event/actionItems.spec.ts @@ -12,7 +12,7 @@ let testEvent: TestEventType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - [, testEvent] = await createTestActionItems(); + [, , testEvent] = await createTestActionItems(); }); afterAll(async () => { diff --git a/tests/resolvers/Mutation/addPledgeToFundraisingCampaign.spec.ts b/tests/resolvers/Mutation/addPledgeToFundraisingCampaign.spec.ts index 39fa99e0bc..759c6f621f 100644 --- a/tests/resolvers/Mutation/addPledgeToFundraisingCampaign.spec.ts +++ b/tests/resolvers/Mutation/addPledgeToFundraisingCampaign.spec.ts @@ -27,7 +27,6 @@ let testUser: TestUserType; let testFund: TestFundType; let testCampaign: InterfaceFundraisingCampaign; let testCampaign2: InterfaceFundraisingCampaign; - let testPledge: TestPledgeType; beforeAll(async () => { @@ -43,7 +42,10 @@ beforeAll(async () => { testFund = temp[2]; testPledge = temp[4]; testCampaign = temp[3]; - testCampaign2 = await createTestFundraisingCampaign(testFund?._id); + testCampaign2 = await createTestFundraisingCampaign( + testFund?._id, + testFund?.organizationId, + ); }); afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); @@ -139,10 +141,10 @@ describe("resolvers->Mutation->addPledgeToFundraisingCampaign", () => { userId: testUser?._id.toString() || "", }; const pledge = await addPledgeToFundraisingCampaign?.({}, args, context); - expect(pledge?.campaigns).toContainEqual(testCampaign2?._id); + expect(pledge?.campaign).toEqual(testCampaign2?._id); const campaign = await FundraisingCampaign.findOne({ _id: testCampaign2?._id, }); - expect(campaign?.pledges).toContainEqual(testPledge?._id); + expect(campaign?.pledges[0]?._id).toEqual(testPledge?._id); }); }); diff --git a/tests/resolvers/Mutation/assignUserTag.spec.ts b/tests/resolvers/Mutation/assignUserTag.spec.ts index 2b5d428144..92281363d2 100644 --- a/tests/resolvers/Mutation/assignUserTag.spec.ts +++ b/tests/resolvers/Mutation/assignUserTag.spec.ts @@ -22,19 +22,26 @@ import { } from "../../../src/constants"; import { AppUserProfile, TagUser } from "../../../src/models"; import type { TestUserTagType } from "../../helpers/tags"; -import { createRootTagWithOrg } from "../../helpers/tags"; +import { + createRootTagWithOrg, + createTwoLevelTagsWithOrg, +} from "../../helpers/tags"; import type { 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 randomUser: TestUserType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - [adminUser, , testTag] = await createRootTagWithOrg(); + [adminUser, , [testTag, testSubTag1]] = await createTwoLevelTagsWithOrg(); + [adminUser2, , testTag2] = await createRootTagWithOrg(); randomUser = await createTestUser(); }); @@ -210,10 +217,36 @@ describe("resolvers -> Mutation -> assignUserTag", () => { }); it(`Tag assign should be successful and the user who has been assigned the tag is returned`, async () => { + const args: MutationAssignUserTagArgs = { + input: { + userId: adminUser2?._id, + tagId: testTag2?._id.toString() ?? "", + }, + }; + const context = { + userId: adminUser2?._id, + }; + + const { assignUserTag: assignUserTagResolver } = await import( + "../../../src/resolvers/Mutation/assignUserTag" + ); + + const payload = await assignUserTagResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(adminUser2?._id.toString()); + + const tagAssigned = await TagUser.exists({ + ...args.input, + }); + + expect(tagAssigned).toBeTruthy(); + }); + + it(`Should assign all the ancestor tags and returns the user that is assigned`, async () => { const args: MutationAssignUserTagArgs = { input: { userId: adminUser?._id, - tagId: testTag?._id.toString() ?? "", + tagId: testSubTag1?._id.toString() ?? "", }, }; const context = { @@ -228,11 +261,17 @@ describe("resolvers -> Mutation -> assignUserTag", () => { expect(payload?._id.toString()).toEqual(adminUser?._id.toString()); - const tagAssigned = await TagUser.exists({ + const subTagAssigned = await TagUser.exists({ ...args.input, }); - expect(tagAssigned).toBeTruthy(); + const ancestorTagAssigned = await TagUser.exists({ + ...args.input, + tagId: testTag?._id.toString() ?? "", + }); + + expect(subTagAssigned).toBeTruthy(); + expect(ancestorTagAssigned).toBeTruthy(); }); it(`Throws USER_ALREADY_HAS_TAG error if tag with _id === args.input.tagId is already assigned to user with _id === args.input.userId`, async () => { diff --git a/tests/resolvers/Mutation/createActionItem.spec.ts b/tests/resolvers/Mutation/createActionItem.spec.ts index e7bd663681..e1093a4975 100644 --- a/tests/resolvers/Mutation/createActionItem.spec.ts +++ b/tests/resolvers/Mutation/createActionItem.spec.ts @@ -259,7 +259,7 @@ describe("resolvers -> Mutation -> createActionItem", () => { expect(createActionItemPayload).toEqual( expect.objectContaining({ - actionItemCategoryId: testCategory?._id, + actionItemCategory: testCategory?._id, }), ); }); @@ -284,7 +284,7 @@ describe("resolvers -> Mutation -> createActionItem", () => { expect(createActionItemPayload).toEqual( expect.objectContaining({ - actionItemCategoryId: testCategory?._id, + actionItemCategory: testCategory?._id, }), ); }); @@ -313,7 +313,7 @@ describe("resolvers -> Mutation -> createActionItem", () => { expect(createActionItemPayload).toEqual( expect.objectContaining({ - actionItemCategoryId: testCategory?._id, + actionItemCategory: testCategory?._id, }), ); }); diff --git a/tests/resolvers/Mutation/createActionItemCategory.spec.ts b/tests/resolvers/Mutation/createActionItemCategory.spec.ts index ba3620dcaa..7c55f65a5b 100644 --- a/tests/resolvers/Mutation/createActionItemCategory.spec.ts +++ b/tests/resolvers/Mutation/createActionItemCategory.spec.ts @@ -47,31 +47,28 @@ describe("resolvers -> Mutation -> createCategory", () => { it(`throws NotFoundError if no user exists with _id === context.userId`, async () => { try { const args: MutationCreateActionItemCategoryArgs = { + isDisabled: false, organizationId: testOrganization?._id, name: "Default", }; - const context = { userId: new Types.ObjectId().toString(), }; - await createActionItemCategoryResolver?.({}, args, context); } catch (error: unknown) { expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); } }); - it(`throws NotFoundError if no organization exists with _id === args.organizationId`, async () => { try { const args: MutationCreateActionItemCategoryArgs = { + isDisabled: false, organizationId: new Types.ObjectId().toString(), name: "Default", }; - const context = { userId: testUser?.id, }; - await createActionItemCategoryResolver?.({}, args, context); } catch (error: unknown) { expect((error as Error).message).toEqual( @@ -79,18 +76,16 @@ describe("resolvers -> Mutation -> createCategory", () => { ); } }); - it(`throws NotAuthorizedError if the user is not a superadmin or the admin of the organization`, async () => { try { const args: MutationCreateActionItemCategoryArgs = { + isDisabled: false, organizationId: testOrganization?._id, name: "Default", }; - const context = { userId: randomUser?.id, }; - await createActionItemCategoryResolver?.({}, args, context); } catch (error: unknown) { expect((error as Error).message).toEqual( @@ -98,23 +93,20 @@ describe("resolvers -> Mutation -> createCategory", () => { ); } }); - it(`creates the actionItemCategory and returns it as an admin`, async () => { const args: MutationCreateActionItemCategoryArgs = { + isDisabled: false, organizationId: testOrganization?._id, name: "Default", }; - const context = { userId: testUser?._id, }; - const createCategoryPayload = await createActionItemCategoryResolver?.( {}, args, context, ); - expect(createCategoryPayload).toEqual( expect.objectContaining({ organizationId: testOrganization?._id, @@ -122,7 +114,6 @@ describe("resolvers -> Mutation -> createCategory", () => { }), ); }); - it(`creates the actionItemCategory and returns it as superAdmin`, async () => { const superAdminTestUser = await AppUserProfile.findOneAndUpdate( { @@ -135,22 +126,19 @@ describe("resolvers -> Mutation -> createCategory", () => { new: true, }, ); - const args: MutationCreateActionItemCategoryArgs = { + isDisabled: false, organizationId: testOrganization?._id, name: "Default2", }; - const context = { userId: superAdminTestUser?.userId, }; - const createCategoryPayload = await createActionItemCategoryResolver?.( {}, args, context, ); - expect(createCategoryPayload).toEqual( expect.objectContaining({ organizationId: testOrganization?._id, @@ -158,18 +146,16 @@ describe("resolvers -> Mutation -> createCategory", () => { }), ); }); - it(`throws ConflictError when the actionItemCategory with given name already exists for the current organization`, async () => { try { const args: MutationCreateActionItemCategoryArgs = { + isDisabled: false, organizationId: testOrganization?._id, name: "Default2", }; - const context = { userId: randomUser?._id, }; - await createActionItemCategoryResolver?.({}, args, context); } catch (error: unknown) { expect((error as Error).message).toEqual( diff --git a/tests/resolvers/Mutation/createFundCampaignPledge.spec.ts b/tests/resolvers/Mutation/createFundCampaignPledge.spec.ts index 81c58b7825..61caaed7c9 100644 --- a/tests/resolvers/Mutation/createFundCampaignPledge.spec.ts +++ b/tests/resolvers/Mutation/createFundCampaignPledge.spec.ts @@ -54,7 +54,6 @@ describe("resolvers->Mutation->createFundraisingCampaignPledge", () => { }; await createFundraisingCampaignPledge?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); } }); @@ -76,7 +75,6 @@ describe("resolvers->Mutation->createFundraisingCampaignPledge", () => { }; await createFundraisingCampaignPledge?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual( FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.MESSAGE, ); @@ -100,7 +98,6 @@ describe("resolvers->Mutation->createFundraisingCampaignPledge", () => { }; await createFundraisingCampaignPledge?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual( START_DATE_VALIDATION_ERROR.MESSAGE, ); @@ -124,7 +121,6 @@ describe("resolvers->Mutation->createFundraisingCampaignPledge", () => { }; await createFundraisingCampaignPledge?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual( END_DATE_VALIDATION_ERROR.MESSAGE, ); diff --git a/tests/resolvers/Mutation/createFundraisingCampaign.spec.ts b/tests/resolvers/Mutation/createFundraisingCampaign.spec.ts index 7e07a04f26..94f49adffc 100644 --- a/tests/resolvers/Mutation/createFundraisingCampaign.spec.ts +++ b/tests/resolvers/Mutation/createFundraisingCampaign.spec.ts @@ -46,6 +46,8 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "testFundraisingCampaign", fundId: testfund?._id, + organizationId: + testfund?.organizationId.toString() || "organizationId", startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", @@ -66,6 +68,8 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "testFundraisingCampaign", fundId: new Types.ObjectId().toString(), + organizationId: + testfund?.organizationId.toString() || "organizationId", startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", @@ -87,6 +91,8 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "testFundraisingCampaign", fundId: testfund?._id, + organizationId: + testfund?.organizationId.toString() || "organizationId", startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", @@ -112,6 +118,8 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "testFundraisingCampaign", fundId: testfund?._id, + organizationId: + testfund?.organizationId.toString() || "organizationId", startDate: "Tue Feb 13 2023", endDate: new Date(new Date().toDateString()), currency: "USD", @@ -135,6 +143,8 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "testFundraisingCampaign", fundId: testfund?._id, + organizationId: + testfund?.organizationId.toString() || "organizationId", startDate: new Date(new Date().toDateString()), endDate: "Tue Feb 13 2023", currency: "USD", @@ -157,6 +167,7 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "testFundraisingCampaign", fundId: testfund?._id, + organizationId: testfund?.organizationId.toString() || "organizationId", startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", @@ -181,6 +192,8 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "test", fundId: testfund?._id, + organizationId: + testfund?.organizationId.toString() || "organizationId", startDate: new Date(), endDate: new Date(new Date().toDateString()), currency: "USD", @@ -212,6 +225,7 @@ describe("resolvers->Mutation->createFundraisingCampaign", () => { data: { name: "testFundraisingCampaign", fundId: testfund?._id, + organizationId: testfund?.organizationId.toString() || "organizationId", startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", diff --git a/tests/resolvers/Mutation/forgotPassword.spec.ts b/tests/resolvers/Mutation/forgotPassword.spec.ts index e5fe828689..415c47f9ee 100644 --- a/tests/resolvers/Mutation/forgotPassword.spec.ts +++ b/tests/resolvers/Mutation/forgotPassword.spec.ts @@ -125,6 +125,40 @@ describe("resolvers -> Mutation -> forgotPassword", () => { } } }); + + it(`Throws an error when otp is invalid in compare function`, async () => { + const otp = "otp"; + + const hashedOtp = await bcrypt.hash(otp + "1", 1); + + const otpToken = jwt.sign( + { + email: testUser?.email ?? "", + otp: hashedOtp, + }, + ACCESS_TOKEN_SECRET as string, + { + expiresIn: 99999999, + }, + ); + + const args: MutationForgotPasswordArgs = { + data: { + newPassword: "newPassword", + otpToken, + userOtp: otp, + }, + }; + + try { + await forgotPasswordResolver?.({}, args, {}); + } catch (error: unknown) { + if (error instanceof Error) { + expect(error.message).toEqual(INVALID_OTP); + } + } + }); + it(`changes the password if args.otp is correct`, async () => { const otp = "otp"; diff --git a/tests/resolvers/Mutation/removeActionItem.spec.ts b/tests/resolvers/Mutation/removeActionItem.spec.ts index 732aba274f..8498afca9f 100644 --- a/tests/resolvers/Mutation/removeActionItem.spec.ts +++ b/tests/resolvers/Mutation/removeActionItem.spec.ts @@ -142,7 +142,7 @@ describe("resolvers -> Mutation -> removeActionItem", () => { // console.log(removedActionItemPayload); expect(removedActionItemPayload).toEqual( expect.objectContaining({ - assigneeId: assignedTestUser?._id, + assignee: assignedTestUser?._id, }), ); }); @@ -152,6 +152,7 @@ describe("resolvers -> Mutation -> removeActionItem", () => { currUserId: testUser?._id, assignedUserId: randomUser?._id, actionItemCategoryId: testCategory?._id, + organizationId: testOrganization?._id, }); const superAdminTestUser = await AppUserProfile.findOneAndUpdate( @@ -182,7 +183,7 @@ describe("resolvers -> Mutation -> removeActionItem", () => { expect(removedActionItemPayload).toEqual( expect.objectContaining({ - assigneeId: randomUser?._id, + assignee: randomUser?._id, }), ); }); @@ -192,6 +193,7 @@ describe("resolvers -> Mutation -> removeActionItem", () => { currUserId: testUser?._id, assignedUserId: randomUser?._id, actionItemCategoryId: testCategory?._id, + organizationId: testOrganization?._id, }); const updatedTestActionItem = await ActionItem.findOneAndUpdate( @@ -199,7 +201,7 @@ describe("resolvers -> Mutation -> removeActionItem", () => { _id: newTestActionItem?._id, }, { - eventId: new Types.ObjectId().toString(), + event: new Types.ObjectId().toString(), }, { new: true, @@ -226,6 +228,7 @@ describe("resolvers -> Mutation -> removeActionItem", () => { currUserId: testUser?._id, assignedUserId: randomUser?._id, actionItemCategoryId: testCategory?._id, + organizationId: testOrganization?._id, }); const updatedTestActionItem = await ActionItem.findOneAndUpdate( @@ -233,7 +236,7 @@ describe("resolvers -> Mutation -> removeActionItem", () => { _id: newTestActionItem?._id, }, { - eventId: testEvent?._id, + event: testEvent?._id, }, { new: true, @@ -256,10 +259,11 @@ describe("resolvers -> Mutation -> removeActionItem", () => { expect(removedActionItemPayload).toEqual( expect.objectContaining({ - assigneeId: randomUser?._id, + assignee: randomUser?._id, }), ); }); + it("throws an error if user does not have appUserProfile", async () => { await AppUserProfile.deleteOne({ userId: testUser?._id, diff --git a/tests/resolvers/Mutation/removeEvent.spec.ts b/tests/resolvers/Mutation/removeEvent.spec.ts index 1e79768240..9c88f9caee 100644 --- a/tests/resolvers/Mutation/removeEvent.spec.ts +++ b/tests/resolvers/Mutation/removeEvent.spec.ts @@ -116,11 +116,7 @@ describe("resolvers -> Mutation -> removeEvent", () => { await removeEventResolver?.({}, args, context); } catch (error: unknown) { expect(spy).toBeCalledWith(EVENT_NOT_FOUND_ERROR.MESSAGE); - if (error instanceof Error) { - EVENT_NOT_FOUND_ERROR.MESSAGE; - } else { - fail(`Expected NotDoundError, but got ${error}`); - } + expect((error as Error).message).toEqual(EVENT_NOT_FOUND_ERROR.MESSAGE); } }); @@ -169,11 +165,9 @@ describe("resolvers -> Mutation -> removeEvent", () => { await removeEventResolver?.({}, args, context); } catch (error: unknown) { expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); - if (error instanceof Error) { - USER_NOT_AUTHORIZED_ERROR.MESSAGE; - } else { - fail(`Expected UnauthorizedError, but got ${error}`); - } + expect((error as Error).message).toEqual( + USER_NOT_AUTHORIZED_ERROR.MESSAGE, + ); } }); @@ -240,7 +234,7 @@ describe("resolvers -> Mutation -> removeEvent", () => { }); it(`removes the events and all action items assiciated with it`, async () => { - [newTestUser, newTestEvent] = await createTestActionItems(); + [newTestUser, , newTestEvent] = await createTestActionItems(); const args: MutationRemoveEventArgs = { id: newTestEvent?.id, diff --git a/tests/resolvers/Mutation/removeFund.spec.ts b/tests/resolvers/Mutation/removeFund.spec.ts deleted file mode 100644 index fad3ccb4c9..0000000000 --- a/tests/resolvers/Mutation/removeFund.spec.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import { - FUND_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../../src/constants"; -import { AppUserProfile, Fund, FundraisingCampaign } from "../../../src/models"; -import { removeFund } from "../../../src/resolvers/Mutation/removeFund"; -import type { TestFundType } from "../../helpers/Fund"; -import { createTestFund } from "../../helpers/Fund"; -import { createTestFundraisingCampaign } from "../../helpers/FundraisingCampaign"; -import { connect, disconnect } from "../../helpers/db"; -import { createTestUser } from "../../helpers/user"; -import type { TestUserType } from "../../helpers/userAndOrg"; -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -// let testCampaign: TestFundCampaignType; -let testFund: TestFundType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const { requestContext } = await import("../../../src/libraries"); - - vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, - ); - - const temp = await createTestFund(); - testUser = temp[0]; - - testFund = temp[2]; -}); -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers->Mutation->removeFund", () => { - it("throw error if no user exists with _id===context.userId", async () => { - try { - const args = { - id: testFund?._id, - }; - const context = { - userId: new Types.ObjectId().toString(), - }; - await removeFund?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); - it("throw error if no fund exists with _id===args.id", async () => { - try { - const args = { - id: new Types.ObjectId().toString(), - }; - const context = { - userId: testUser?._id, - }; - await removeFund?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(FUND_NOT_FOUND_ERROR.MESSAGE); - } - }); - it("throw error if user is not admin of the organization", async () => { - try { - const args = { - id: testFund?._id, - }; - const randomUser = await createTestUser(); - const context = { - userId: randomUser?._id, - }; - await removeFund?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - it("deletes the fund", async () => { - const args = { - id: testFund?._id, - }; - const context = { - userId: testUser?._id, - }; - try { - await removeFund?.({}, args, context); - const fund = await Fund.findOne({ _id: testFund?._id }); - expect(fund).toBeNull(); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - it("deletes all the campaigns associated with the fund", async () => { - const temp = await createTestFund(); - const testfund2 = temp[2]; - const testUser2 = temp[0]; - const args = { - id: testfund2?._id, - }; - const context = { - userId: testUser2?._id, - }; - const campaign = await createTestFundraisingCampaign(testfund2?._id); - - try { - await removeFund?.({}, args, context); - const fund = await Fund.findOne({ _id: testfund2?._id }); - expect(fund).toBeNull(); - const campaignFound = await FundraisingCampaign.findOne({ - _id: campaign?._id, - }); - - expect(campaignFound).toBeNull(); - } catch (error) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - - it("throws an error if the user does not have appUserProfile", async () => { - await AppUserProfile.deleteOne({ - userId: testUser?._id, - }); - const args = { - id: testFund?._id, - }; - - const context = { - userId: testUser?._id, - }; - - try { - await removeFund?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); -}); diff --git a/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts b/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts index ff1b994364..28bc0c8628 100644 --- a/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts +++ b/tests/resolvers/Mutation/removeFundCampaignPledge.spec.ts @@ -40,7 +40,7 @@ afterAll(async () => { await disconnect(MONGOOSE_INSTANCE); }); -describe("resolvers->Mutation->removeFund", () => { +describe("resolvers->Mutation->removeFundraisingCampaignPledge", () => { it("throw error if no user exists with _id===context.userId", async () => { try { const args: MutationRemoveFundraisingCampaignPledgeArgs = { diff --git a/tests/resolvers/Mutation/removeFundraisingCampaign.spec.ts b/tests/resolvers/Mutation/removeFundraisingCampaign.spec.ts deleted file mode 100644 index 366efe6403..0000000000 --- a/tests/resolvers/Mutation/removeFundraisingCampaign.spec.ts +++ /dev/null @@ -1,170 +0,0 @@ -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import { - FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR, - FUND_NOT_FOUND_ERROR, - USER_NOT_AUTHORIZED_ERROR, - USER_NOT_FOUND_ERROR, -} from "../../../src/constants"; -import { - AppUserProfile, - Fund, - FundraisingCampaign, - type InterfaceFundraisingCampaign, -} from "../../../src/models"; -import { removeFundraisingCampaign } from "../../../src/resolvers/Mutation/removeFundraisingCampaign"; -import type { TestFundType } from "../../helpers/Fund"; -import { createTestFund } from "../../helpers/Fund"; -import { createTestFundraisingCampaign } from "../../helpers/FundraisingCampaign"; -import { connect, disconnect } from "../../helpers/db"; -import { createTestUser } from "../../helpers/user"; -import type { TestUserType } from "../../helpers/userAndOrg"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: TestUserType; -let testCampaign: InterfaceFundraisingCampaign; -let testFund: TestFundType; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const { requestContext } = await import("../../../src/libraries"); - - vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, - ); - - const temp = await createTestFund(); - testUser = temp[0]; - testFund = temp[2]; - testCampaign = await createTestFundraisingCampaign(testFund?._id); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); - -describe("resolvers->Mutation->removeFundraisingCampaign", () => { - it("throws an error if no user exists with _id===context.userId", async () => { - try { - const args = { - id: testFund?._id, - }; - const context = { - userId: new Types.ObjectId().toString(), - }; - await removeFundraisingCampaign?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it("throws an error if no fund campaign exists with _id===args.id", async () => { - try { - const args = { - id: new Types.ObjectId().toString(), - }; - const context = { - userId: testUser?._id, - }; - await removeFundraisingCampaign?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.MESSAGE, - ); - } - }); - - it("throws an error if no fund exists with _id===campaign.fundId", async () => { - try { - const campaign = await createTestFundraisingCampaign( - new Types.ObjectId().toString(), - ); - const args = { - id: campaign?._id.toString() || "", - }; - const context = { - userId: testUser?._id, - }; - await removeFundraisingCampaign?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual(FUND_NOT_FOUND_ERROR.MESSAGE); - } - }); - - it("throws an error if the user is not admin of the organization or superadmin", async () => { - try { - const args = { - id: testCampaign?._id.toString(), - }; - const randomUser = await createTestUser(); - const context = { - userId: randomUser?._id, - }; - await removeFundraisingCampaign?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); - - it("deletes the fundraising campaign", async () => { - await AppUserProfile.findOneAndUpdate( - { userId: testUser?._id }, - { - $set: { - adminFor: [testFund?.organizationId.toString()], - isSuperAdmin: true, - }, - }, - { new: true, upsert: true }, - ); - const args = { id: testCampaign._id.toString() }; - const context = { userId: testUser?._id.toString() }; - - await removeFundraisingCampaign?.({}, args, context); - const deletedCampaign = await FundraisingCampaign.findById(args.id); - expect(deletedCampaign).toBeNull(); - }); - - it("removes the campaign from the fund", async () => { - await AppUserProfile.findOneAndUpdate( - { userId: testUser?._id }, - { - $set: { - adminFor: [testFund?.organizationId.toString()], - isSuperAdmin: true, - }, - }, - { new: true, upsert: true }, - ); - const newCampaign = await createTestFundraisingCampaign(testFund?._id); // Ensuring a fresh campaign for this test - const args = { id: newCampaign._id.toString() }; - const context = { userId: testUser?._id.toString() }; - - await removeFundraisingCampaign?.({}, args, context); - const fundAfterDeletion = await Fund.findById(testFund?._id); - expect(fundAfterDeletion?.campaigns).not.toContainEqual( - new Types.ObjectId(args.id), - ); - }); - it("throws an error if the user does not have appUserProfile", async () => { - await AppUserProfile.deleteOne({ userId: testUser?._id }); - testCampaign = await createTestFundraisingCampaign(testFund?._id); - const args = { - id: testCampaign?._id.toString() || "", - }; - const context = { - userId: testUser?._id.toString() || "", - }; - - try { - await removeFundraisingCampaign?.({}, args, context); - } catch (error: unknown) { - expect((error as Error).message).toEqual( - USER_NOT_AUTHORIZED_ERROR.MESSAGE, - ); - } - }); -}); diff --git a/tests/resolvers/Mutation/removeOrganization.spec.ts b/tests/resolvers/Mutation/removeOrganization.spec.ts index b89438cdc3..f2dfad353c 100644 --- a/tests/resolvers/Mutation/removeOrganization.spec.ts +++ b/tests/resolvers/Mutation/removeOrganization.spec.ts @@ -143,10 +143,11 @@ beforeAll(async () => { }); testActionItem = await ActionItem.create({ - creatorId: testUsers[0]?._id, - assigneeId: testUsers[1]?._id, - assignerId: testUsers[0]?._id, - actionItemCategoryId: testCategory?._id, + creator: testUsers[0]?._id, + assignee: testUsers[1]?._id, + assigner: testUsers[0]?._id, + actionItemCategory: testCategory?._id, + organization: testOrganization?._id, }); await Organization.updateOne( diff --git a/tests/resolvers/Mutation/unassignUserTag.spec.ts b/tests/resolvers/Mutation/unassignUserTag.spec.ts index 5a6ea938d7..16ce061c8f 100644 --- a/tests/resolvers/Mutation/unassignUserTag.spec.ts +++ b/tests/resolvers/Mutation/unassignUserTag.spec.ts @@ -21,7 +21,7 @@ import { } from "../../../src/constants"; import { AppUserProfile, TagUser } from "../../../src/models"; import type { TestUserTagType } from "../../helpers/tags"; -import { createRootTagWithOrg } from "../../helpers/tags"; +import { createTwoLevelTagsWithOrg } from "../../helpers/tags"; import type { TestUserType } from "../../helpers/userAndOrg"; import { createTestUser } from "../../helpers/userAndOrg"; @@ -29,11 +29,12 @@ let MONGOOSE_INSTANCE: typeof mongoose; let adminUser: TestUserType; let testTag: TestUserTagType; +let testSubTag1: TestUserTagType; let randomUser: TestUserType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - [adminUser, , testTag] = await createRootTagWithOrg(); + [adminUser, , [testTag, testSubTag1]] = await createTwoLevelTagsWithOrg(); randomUser = await createTestUser(); }); @@ -244,6 +245,56 @@ describe("resolvers -> Mutation -> unassignUserTag", () => { expect(tagAssigned).toBeFalsy(); }); + + it(`should unassign all the child tags and decendent tags of a parent tag and return the user`, async () => { + const { requestContext } = await import("../../../src/libraries"); + + vi.spyOn(requestContext, "translate").mockImplementationOnce( + (message) => `Translated ${message}`, + ); + + const args: MutationUnassignUserTagArgs = { + input: { + userId: adminUser?._id, + tagId: testTag ? testTag._id.toString() : "", + }, + }; + const context = { + userId: adminUser?._id, + }; + + // Assign the parent and sub tag to the user + await TagUser.create({ + ...args.input, + }); + + await TagUser.create({ + ...args.input, + tagId: testSubTag1 ? testSubTag1._id.toString() : "", + }); + + // Test the unassignUserTag resolver + const { unassignUserTag: unassignUserTagResolver } = await import( + "../../../src/resolvers/Mutation/unassignUserTag" + ); + + const payload = await unassignUserTagResolver?.({}, args, context); + + expect(payload?._id.toString()).toEqual(adminUser?._id.toString()); + + const tagAssigned = await TagUser.exists({ + ...args.input, + }); + + const subTagAssigned = await TagUser.exists({ + ...args.input, + tagId: testSubTag1 ? testSubTag1._id.toString() : "", + }); + + expect(tagAssigned).toBeFalsy(); + expect(subTagAssigned).toBeFalsy(); + }); + it("throws an error if the user does not have appUserProfile", async () => { const { requestContext } = await import("../../../src/libraries"); const spy = vi diff --git a/tests/resolvers/Mutation/updateActionItem.spec.ts b/tests/resolvers/Mutation/updateActionItem.spec.ts index fa0d6a77fc..b7406cd6fa 100644 --- a/tests/resolvers/Mutation/updateActionItem.spec.ts +++ b/tests/resolvers/Mutation/updateActionItem.spec.ts @@ -191,8 +191,8 @@ describe("resolvers -> Mutation -> updateActionItem", () => { expect(updatedActionItemPayload).toEqual( expect.objectContaining({ - assigneeId: assignedTestUser?._id, - actionItemCategoryId: testCategory?._id, + assignee: assignedTestUser?._id, + actionItemCategory: testCategory?._id, }), ); }); @@ -229,8 +229,8 @@ describe("resolvers -> Mutation -> updateActionItem", () => { expect(updatedActionItemPayload).toEqual( expect.objectContaining({ - assigneeId: testUser?._id, - actionItemCategoryId: testCategory?._id, + assignee: testUser?._id, + actionItemCategory: testCategory?._id, }), ); }); @@ -241,7 +241,7 @@ describe("resolvers -> Mutation -> updateActionItem", () => { _id: testActionItem?._id, }, { - eventId: new Types.ObjectId().toString(), + event: new Types.ObjectId().toString(), }, { new: true, @@ -281,7 +281,7 @@ describe("resolvers -> Mutation -> updateActionItem", () => { _id: testActionItem?._id, }, { - eventId: testEvent?._id, + event: testEvent?._id, }, { new: true, @@ -290,7 +290,7 @@ describe("resolvers -> Mutation -> updateActionItem", () => { const args: MutationUpdateActionItemArgs = { data: { - assigneeId: testUser?._id, + isCompleted: true, }, id: updatedTestActionItem?._id.toString() ?? "", }; @@ -307,8 +307,8 @@ describe("resolvers -> Mutation -> updateActionItem", () => { expect(updatedActionItemPayload).toEqual( expect.objectContaining({ - actionItemCategoryId: testCategory?._id, - assigneeId: testUser?._id, + actionItemCategory: testCategory?._id, + isCompleted: true, }), ); }); diff --git a/tests/resolvers/Mutation/updateCommunity.spec.ts b/tests/resolvers/Mutation/updateCommunity.spec.ts index f7e74bf0ff..1af5df7d54 100644 --- a/tests/resolvers/Mutation/updateCommunity.spec.ts +++ b/tests/resolvers/Mutation/updateCommunity.spec.ts @@ -39,7 +39,7 @@ const args: MutationUpdateCommunityArgs = { linkedIn: "http://testurl.com", reddit: "http://testurl.com", slack: "http://testurl.com", - twitter: "http://testurl.com", + X: "http://testurl.com", youTube: "http://testurl.com", }, websiteLink: "http://testlink.com", @@ -162,7 +162,7 @@ describe("resolvers -> Mutation -> updateCommunity", () => { linkedIn: "", reddit: "", slack: "", - twitter: "", + X: "", youTube: "", }, websiteLink: "", diff --git a/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts b/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts index 096c57e5c5..cbf74e3b8e 100644 --- a/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts +++ b/tests/resolvers/Mutation/updateFundCampaignPledge.spec.ts @@ -16,6 +16,7 @@ import { } from "../../helpers/FundraisingCampaignPledge"; import { connect, disconnect } from "../../helpers/db"; import type { TestUserType } from "../../helpers/user"; +import { createTestUser } from "../../helpers/userAndOrg"; let testUser: TestUserType; let testcampaignPledge: TestPledgeType; @@ -28,6 +29,7 @@ beforeAll(async () => { vi.spyOn(requestContext, "translate").mockImplementation( (message) => message, ); + const temp = await createTestFundraisingCampaignPledge(); testUser = temp[0]; testFundraisingCampaign = temp[3]; @@ -123,9 +125,11 @@ describe("resolvers->Mutation->updateFundCampaignPledge", () => { }); it("should update the pledge", async () => { + const testUser2 = await createTestUser(); const args: MutationUpdateFundraisingCampaignPledgeArgs = { id: testcampaignPledge?._id.toString() || "", data: { + users: [testUser2?._id.toString()], startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", @@ -137,6 +141,10 @@ describe("resolvers->Mutation->updateFundCampaignPledge", () => { }; const pledge = await updateFundraisingCampaignPledge?.({}, args, context); expect(pledge).toBeTruthy(); + expect(pledge?.amount).toEqual(1000); + expect(pledge?.currency).toEqual("USD"); + expect(pledge?.users[0]).toEqual(testUser2?._id); + expect(pledge?.users).not.toContain(testUser?._id); }); it("throws an error when an invalid user ID is provided", async () => { diff --git a/tests/resolvers/Mutation/updateFundraisingCampaign.spec.ts b/tests/resolvers/Mutation/updateFundraisingCampaign.spec.ts index 7dd91bbf23..ae1f52028f 100644 --- a/tests/resolvers/Mutation/updateFundraisingCampaign.spec.ts +++ b/tests/resolvers/Mutation/updateFundraisingCampaign.spec.ts @@ -87,7 +87,6 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { await updateFundraisingCampaign?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual( FUNDRAISING_CAMPAIGN_NOT_FOUND_ERROR.MESSAGE, ); @@ -117,7 +116,6 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { await updateFundraisingCampaign?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual(FUND_NOT_FOUND_ERROR.MESSAGE); } }); @@ -143,7 +141,6 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { await updateFundraisingCampaign?.({}, args, context); } catch (error: unknown) { - // console.log((error as Error).message); expect((error as Error).message).toEqual( USER_NOT_AUTHORIZED_ERROR.MESSAGE, ); @@ -169,7 +166,6 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { await updateFundraisingCampaign?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual( START_DATE_VALIDATION_ERROR.MESSAGE, ); @@ -182,7 +178,6 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { id: testFundraisingCampaign?._id.toString() || "", data: { name: "testFundraisingCampaign", - startDate: new Date(new Date().toDateString()), endDate: "Tue Feb 13 2023", currency: "USD", @@ -196,7 +191,6 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { await updateFundraisingCampaign?.({}, args, context); } catch (error: unknown) { - // console.log(error); expect((error as Error).message).toEqual( END_DATE_VALIDATION_ERROR.MESSAGE, ); @@ -233,7 +227,7 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { const args: MutationUpdateFundraisingCampaignArgs = { id: testFundraisingCampaign?._id.toString() || "", data: { - name: "testFundraisingCampaign", + name: testFundraisingCampaign?.name + "2" || "", startDate: new Date(new Date().toDateString()), endDate: new Date(new Date().toDateString()), currency: "USD", @@ -246,7 +240,7 @@ describe("resolvers->Mutation->updateFundrasingCampaign", () => { }; const result = await updateFundraisingCampaign?.({}, args, context); - expect(result?.name).toEqual("testFundraisingCampaign"); + expect(result?.name).toEqual(testFundraisingCampaign?.name + "2"); }); it("throws an error if the user does not have appUserProfile", async () => { diff --git a/tests/resolvers/Mutation/updateUserTag.spec.ts b/tests/resolvers/Mutation/updateUserTag.spec.ts index 60f2099498..87280b4bc3 100644 --- a/tests/resolvers/Mutation/updateUserTag.spec.ts +++ b/tests/resolvers/Mutation/updateUserTag.spec.ts @@ -60,7 +60,7 @@ describe("resolvers -> Mutation -> updateUserTag", () => { const args: MutationUpdateUserTagArgs = { input: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - _id: testTag!._id.toString(), + tagId: testTag!._id.toString(), name: "NewName", tagColor: "#000000", }, @@ -92,7 +92,7 @@ describe("resolvers -> Mutation -> updateUserTag", () => { try { const args: MutationUpdateUserTagArgs = { input: { - _id: new Types.ObjectId().toString(), + tagId: new Types.ObjectId().toString(), name: "NewName", tagColor: "#000000", }, @@ -125,7 +125,7 @@ describe("resolvers -> Mutation -> updateUserTag", () => { const args: MutationUpdateUserTagArgs = { input: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - _id: testTag!._id.toString(), + tagId: testTag!._id.toString(), name: "NewName", tagColor: "#000000", }, @@ -162,7 +162,7 @@ describe("resolvers -> Mutation -> updateUserTag", () => { const args: MutationUpdateUserTagArgs = { input: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - _id: testTag!._id.toString(), + tagId: testTag!._id.toString(), // eslint-disable-next-line @typescript-eslint/no-non-null-assertion name: testTag!.name, tagColor: "#000000", @@ -198,7 +198,7 @@ describe("resolvers -> Mutation -> updateUserTag", () => { const args: MutationUpdateUserTagArgs = { input: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - _id: testTag!._id.toString(), + tagId: testTag!._id.toString(), // eslint-disable-next-line @typescript-eslint/no-non-null-assertion name: testTag2!.name, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -226,7 +226,7 @@ describe("resolvers -> Mutation -> updateUserTag", () => { const args: MutationUpdateUserTagArgs = { input: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - _id: testTag!._id.toString(), + tagId: testTag!._id.toString(), name: "NewName", tagColor: "#000000", }, diff --git a/tests/resolvers/Post/likedBy.spec.ts b/tests/resolvers/Post/likedBy.spec.ts new file mode 100644 index 0000000000..5c768353ee --- /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/actionItemsByEvent.spec.ts b/tests/resolvers/Query/actionItemsByEvent.spec.ts index 9f5dc830e6..7ea397345b 100644 --- a/tests/resolvers/Query/actionItemsByEvent.spec.ts +++ b/tests/resolvers/Query/actionItemsByEvent.spec.ts @@ -13,7 +13,7 @@ let testEvent: TestEventType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - [, testEvent] = await createTestActionItems(); + [, , testEvent] = await createTestActionItems(); }); afterAll(async () => { @@ -33,7 +33,7 @@ describe("resolvers -> Query -> actionItemsByEvent", () => { ); const actionItemsByEventInfo = await ActionItem.find({ - eventId: testEvent?._id, + event: testEvent?._id, }).lean(); expect(actionItemsByEventPayload).toEqual(actionItemsByEventInfo); diff --git a/tests/resolvers/Query/actionItemsByOrganization.spec.ts b/tests/resolvers/Query/actionItemsByOrganization.spec.ts index 9d450d8ad1..8437ad5cdd 100644 --- a/tests/resolvers/Query/actionItemsByOrganization.spec.ts +++ b/tests/resolvers/Query/actionItemsByOrganization.spec.ts @@ -1,4 +1,5 @@ import "dotenv/config"; +import type { InterfaceActionItem } from "../../../src/models"; import { ActionItem, ActionItemCategory } from "../../../src/models"; import { connect, disconnect } from "../../helpers/db"; import type { @@ -10,16 +11,21 @@ import { actionItemsByOrganization as actionItemsByOrganizationResolver } from " import { beforeAll, afterAll, describe, it, expect } from "vitest"; import type mongoose from "mongoose"; import { createTestActionItems } from "../../helpers/actionItem"; -import type { TestOrganizationType } from "../../helpers/userAndOrg"; +import type { + TestOrganizationType, + TestUserType, +} from "../../helpers/userAndOrg"; import type { TestEventType } from "../../helpers/events"; let MONGOOSE_INSTANCE: typeof mongoose; let testOrganization: TestOrganizationType; let testEvent: TestEventType; +let testAssigneeUser: TestUserType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - [, testEvent, testOrganization] = await createTestActionItems(); + [, testAssigneeUser, testEvent, testOrganization] = + await createTestActionItems(); }); afterAll(async () => { @@ -27,30 +33,59 @@ afterAll(async () => { }); describe("resolvers -> Query -> actionItemsByOrganization", () => { - it(`returns list of all action items associated with an organization in ascending order`, async () => { + 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, {}); + (await actionItemsByOrganizationResolver?.( + {}, + args, + {}, + )) as InterfaceActionItem[]; - const actionItemCategories = await ActionItemCategory.find({ - organizationId: args.organizationId, - }); - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, + 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({ - actionItemCategoryId: { $in: actionItemCategoriesIds }, + organization: args.organizationId, + event: args.eventId, }).lean(); - expect(actionItemsByOrganizationPayload).toEqual( - actionItemsByOrganizationInfo, + expect(actionItemsByOrganizationPayload[0]).toEqual( + expect.objectContaining({ + _id: actionItemsByOrganizationInfo[0]._id, + }), ); }); @@ -59,27 +94,26 @@ describe("resolvers -> Query -> actionItemsByOrganization", () => { const args: QueryActionItemsByOrganizationArgs = { organizationId: testOrganization?._id, + eventId: null, orderBy, }; const actionItemsByOrganizationPayload = - await actionItemsByOrganizationResolver?.({}, args, {}); - - actionItemsByOrganizationPayload?.reverse(); - - const actionItemCategories = await ActionItemCategory.find({ - organizationId: args.organizationId, - }); - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, - ); + (await actionItemsByOrganizationResolver?.( + {}, + args, + {}, + )) as InterfaceActionItem[]; const actionItemsByOrganizationInfo = await ActionItem.find({ - actionItemCategoryId: { $in: actionItemCategoriesIds }, + organization: args.organizationId, + event: args.eventId, }).lean(); - expect(actionItemsByOrganizationPayload).toEqual( - actionItemsByOrganizationInfo, + expect(actionItemsByOrganizationPayload[0]).toEqual( + expect.objectContaining({ + _id: actionItemsByOrganizationInfo[0]._id, + }), ); }); @@ -116,31 +150,31 @@ describe("resolvers -> Query -> actionItemsByOrganization", () => { }); it(`returns list of all action items associated with an organization that are active`, async () => { const where: ActionItemWhereInput = { - is_active: true, + is_completed: false, }; const args: QueryActionItemsByOrganizationArgs = { organizationId: testOrganization?._id, + eventId: testEvent?._id, where, }; const actionItemsByOrganizationPayload = - await actionItemsByOrganizationResolver?.({}, args, {}); - - const actionItemCategories = await ActionItemCategory.find({ - organizationId: args.organizationId, - }); - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, - ); + (await actionItemsByOrganizationResolver?.( + {}, + args, + {}, + )) as InterfaceActionItem[]; const actionItemsByOrganizationInfo = await ActionItem.find({ - actionItemCategoryId: { $in: actionItemCategoriesIds }, - isCompleted: false, + organization: args.organizationId, + event: args.eventId, }).lean(); - expect(actionItemsByOrganizationPayload).toEqual( - actionItemsByOrganizationInfo, + expect(actionItemsByOrganizationPayload[0]).toEqual( + expect.objectContaining({ + _id: actionItemsByOrganizationInfo[1]._id, + }), ); }); @@ -151,91 +185,86 @@ describe("resolvers -> Query -> actionItemsByOrganization", () => { const args: QueryActionItemsByOrganizationArgs = { organizationId: testOrganization?._id, + eventId: testEvent?._id, where, }; const actionItemsByOrganizationPayload = - await actionItemsByOrganizationResolver?.({}, args, {}); - - const actionItemCategories = await ActionItemCategory.find({ - organizationId: args.organizationId, - }); - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, - ); + (await actionItemsByOrganizationResolver?.( + {}, + args, + {}, + )) as InterfaceActionItem[]; const actionItemsByOrganizationInfo = await ActionItem.find({ - actionItemCategoryId: { $in: actionItemCategoriesIds }, - isCompleted: true, + organization: args.organizationId, + event: args.eventId, }).lean(); - expect(actionItemsByOrganizationPayload).toEqual( - actionItemsByOrganizationInfo, + expect(actionItemsByOrganizationPayload[0]).toEqual( + expect.objectContaining({ + _id: actionItemsByOrganizationInfo[0]._id, + }), ); }); - it(`returns list of all action items associated with an organization and belonging to an event`, async () => { + it(`returns list of all action items matching categoryName Filter`, async () => { const where: ActionItemWhereInput = { - event_id: testEvent?._id, + categoryName: "Default", }; const args: QueryActionItemsByOrganizationArgs = { organizationId: testOrganization?._id, + eventId: testEvent?._id, where, }; const actionItemsByOrganizationPayload = - await actionItemsByOrganizationResolver?.({}, args, {}); - - const actionItemCategories = await ActionItemCategory.find({ - organizationId: args.organizationId, - }); - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, - ); + (await actionItemsByOrganizationResolver?.( + {}, + args, + {}, + )) as InterfaceActionItem[]; const actionItemsByOrganizationInfo = await ActionItem.find({ - actionItemCategoryId: { $in: actionItemCategoriesIds }, - eventId: testEvent?._id, + organization: args.organizationId, + event: args.eventId, }).lean(); - expect(actionItemsByOrganizationPayload).toEqual( - actionItemsByOrganizationInfo, + expect(actionItemsByOrganizationPayload[0].actionItemCategory).toEqual( + expect.objectContaining({ + _id: actionItemsByOrganizationInfo[0].actionItemCategory, + }), ); }); - it(`returns list of all action items associated with an organization and belonging to an event and a specific category and are completed`, async () => { - const actionItemCategories = await ActionItemCategory.find({ - organizationId: testOrganization?._id, - }); - const actionItemCategoriesIds = actionItemCategories.map( - (category) => category._id, - ); - - const actionItemCategoryId = actionItemCategoriesIds[0]; - + it(`returns list of all action items matching assigneeName Filter`, async () => { const where: ActionItemWhereInput = { - actionItemCategory_id: actionItemCategoryId.toString(), - event_id: testEvent?._id, - is_completed: true, + assigneeName: testAssigneeUser?.firstName, }; const args: QueryActionItemsByOrganizationArgs = { organizationId: testOrganization?._id, + eventId: testEvent?._id, where, }; const actionItemsByOrganizationPayload = - await actionItemsByOrganizationResolver?.({}, args, {}); + (await actionItemsByOrganizationResolver?.( + {}, + args, + {}, + )) as InterfaceActionItem[]; const actionItemsByOrganizationInfo = await ActionItem.find({ - actionItemCategoryId, - eventId: testEvent?._id, - isCompleted: true, + organization: args.organizationId, + event: args.eventId, }).lean(); - expect(actionItemsByOrganizationPayload).toEqual( - actionItemsByOrganizationInfo, + expect(actionItemsByOrganizationPayload[0].assignee).toEqual( + expect.objectContaining({ + _id: actionItemsByOrganizationInfo[0].assignee, + }), ); }); }); diff --git a/tests/resolvers/Query/directChatById.spec.ts b/tests/resolvers/Query/directChatById.spec.ts index 0b6e993df1..5717c5b56c 100644 --- a/tests/resolvers/Query/directChatById.spec.ts +++ b/tests/resolvers/Query/directChatById.spec.ts @@ -39,7 +39,7 @@ describe("resolvers -> Query -> directChatsById", () => { it(`returns list of all directChats with directChat.users containing the user with _id === args.id`, async () => { const args: QueryDirectChatsByUserIdArgs = { - id: testDirectChat?._id, + id: testDirectChat?._id.toString() ?? "defaultString", }; const directChatsByUserIdPayload = await directChatByIdResolver?.( diff --git a/tests/resolvers/Query/getEventVolunteerGroups.spec.ts b/tests/resolvers/Query/getEventVolunteerGroups.spec.ts new file mode 100644 index 0000000000..a52a9a49d4 --- /dev/null +++ b/tests/resolvers/Query/getEventVolunteerGroups.spec.ts @@ -0,0 +1,55 @@ +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, +} from "../../helpers/events"; +import { createTestEventVolunteerGroup } from "../../helpers/events"; +import type { EventVolunteerGroup } from "../../../src/types/generatedGraphQLTypes"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testEvent: TestEventType; +let testEventVolunteerGroup: TestEventVolunteerGroupType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const temp = await createTestEventVolunteerGroup(); + testEvent = temp[2]; + testEventVolunteerGroup = temp[4]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Query -> getEventVolunteerGroups", () => { + it(`returns list of all existing event volunteer groups with eventId === args.where.eventId`, async () => { + const volunteerGroupsPayload = (await getEventVolunteerGroups?.( + {}, + { + where: { + eventId: testEvent?._id, + }, + }, + {}, + )) as unknown as EventVolunteerGroup[]; + + expect(volunteerGroupsPayload[0]._id).toEqual(testEventVolunteerGroup._id); + }); + + it(`returns empty list of all existing event volunteer groups with eventId !== args.where.eventId`, async () => { + const volunteerGroupsPayload = (await getEventVolunteerGroups?.( + {}, + { + where: { + eventId: "123456789012345678901234", + }, + }, + {}, + )) as unknown as EventVolunteerGroup[]; + + expect(volunteerGroupsPayload).toEqual([]); + }); +}); diff --git a/tests/resolvers/Query/getFundCampaignById.spec.ts b/tests/resolvers/Query/getFundCampaignById.spec.ts deleted file mode 100644 index 3a8a7dbed8..0000000000 --- a/tests/resolvers/Query/getFundCampaignById.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type mongoose from "mongoose"; -import { Types } from "mongoose"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import type { InterfaceFundraisingCampaign } from "../../../src/models"; -import { getFundraisingCampaignById } from "../../../src/resolvers/Query/getFundraisingCampaign"; -import { createTestFund, type TestFundType } from "../../helpers/Fund"; -import { createTestFundraisingCampaign } from "../../helpers/FundraisingCampaign"; -import { connect, disconnect } from "../../helpers/db"; -import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; - -let MONGOOSE_INSTANCE: typeof mongoose; -let testFund: TestFundType; -let testCampaign: InterfaceFundraisingCampaign; - -beforeAll(async () => { - MONGOOSE_INSTANCE = await connect(); - const resultArray = await createTestFund(); - testFund = resultArray[2]; - testCampaign = await createTestFundraisingCampaign(testFund?._id); -}); - -afterAll(async () => { - await disconnect(MONGOOSE_INSTANCE); -}); -describe("resolvers->Query->getFundCampaignById", () => { - it(`returns the campaign with _id === args.id`, async () => { - const args = { - id: testCampaign?._id.toString(), - }; - const pledges = await FundraisingCampaignPledge.find({ - campaigns: testCampaign?._id, - }).lean(); - console.log(pledges); - const getFundCampaignByIdPayload = await getFundraisingCampaignById?.( - {}, - args, - {}, - ); - expect(getFundCampaignByIdPayload?._id).toEqual(testCampaign._id); - }); - - it(`returns null if campaign not found for args.id`, async () => { - const args = { - id: new Types.ObjectId().toString(), - }; - const getFundCampaignByIdPayload = await getFundraisingCampaignById?.( - {}, - args, - {}, - ); - expect(getFundCampaignByIdPayload).toEqual({}); - }); -}); diff --git a/tests/resolvers/Query/getFundRaisingCampaigns.spec.ts b/tests/resolvers/Query/getFundRaisingCampaigns.spec.ts new file mode 100644 index 0000000000..155649b59e --- /dev/null +++ b/tests/resolvers/Query/getFundRaisingCampaigns.spec.ts @@ -0,0 +1,76 @@ +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import type { InterfaceFundraisingCampaign } from "../../../src/models"; +import { getFundraisingCampaigns } from "../../../src/resolvers/Query/getFundraisingCampaigns"; +import { createTestFund, type TestFundType } from "../../helpers/Fund"; +import { createTestFundraisingCampaign } from "../../helpers/FundraisingCampaign"; +import { connect, disconnect } from "../../helpers/db"; +import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testFund: TestFundType; +let testCampaign: InterfaceFundraisingCampaign; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestFund(); + testFund = resultArray[2]; + testCampaign = await createTestFundraisingCampaign( + testFund?._id, + testFund?.organizationId, + ["fundId"], + ); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers->Query->getFundraisingCampaigns", () => { + it(`returns the campaign with _id === args.where.id`, async () => { + const args = { + where: { + id: testCampaign?._id.toString(), + }, + }; + const pledges = await FundraisingCampaignPledge.find({ + campaigns: testCampaign?._id, + }).lean(); + console.log(pledges); + const getFundraisingCampaignsPayload = (await getFundraisingCampaigns?.( + {}, + args, + {}, + )) as InterfaceFundraisingCampaign[]; + expect(getFundraisingCampaignsPayload[0]._id).toEqual(testCampaign._id); + }); + + it(`returns the campaign with belong to a organization`, async () => { + const args = { + where: { + organizationId: testCampaign?.organizationId.toString(), + }, + }; + const getFundraisingCampaignsPayload = (await getFundraisingCampaigns?.( + {}, + args, + {}, + )) as InterfaceFundraisingCampaign[]; + expect(getFundraisingCampaignsPayload[0]._id).toEqual(testCampaign._id); + }); + + it(`returns null if campaign not found for args.id`, async () => { + const args = { + where: { + id: new Types.ObjectId().toString(), + }, + }; + const getFundraisingCampaignsPayload = await getFundraisingCampaigns?.( + {}, + args, + {}, + ); + expect(getFundraisingCampaignsPayload).toEqual([]); + }); +}); diff --git a/tests/resolvers/Query/getPledgesByUserId.spec.ts b/tests/resolvers/Query/getPledgesByUserId.spec.ts new file mode 100644 index 0000000000..4b375da8c1 --- /dev/null +++ b/tests/resolvers/Query/getPledgesByUserId.spec.ts @@ -0,0 +1,129 @@ +import type mongoose from "mongoose"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { USER_NOT_AUTHORIZED_ERROR } from "../../../src/constants"; +import { + type InterfaceUser, + AppUserProfile, + type InterfaceFundraisingCampaign, +} from "../../../src/models"; + +import { getPledgesByUserId } from "../../../src/resolvers/Query/getPledgesByUserId"; +import { connect, disconnect } from "../../helpers/db"; +import type { TestUserType } from "../../helpers/userAndOrg"; +import type { TestPledgeType } from "../../helpers/FundraisingCampaignPledge"; +import { createTestFundraisingCampaignPledge } from "../../helpers/FundraisingCampaignPledge"; +import type { InterfaceFundraisingCampaignPledges } from "../../../src/models/FundraisingCampaignPledge"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testUser: TestUserType; +let testCampaign: InterfaceFundraisingCampaign; +let testPledge: TestPledgeType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const { requestContext } = await import("../../../src/libraries"); + + vi.spyOn(requestContext, "translate").mockImplementation( + (message) => message, + ); + + const temp = await createTestFundraisingCampaignPledge(); + testUser = temp[0]; + testPledge = temp[4]; + testCampaign = temp[3]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers->Query->getPledgesByUserId", () => { + it("Returns all the pledges belonging to specific userId", async () => { + const args = { userId: testUser?._id.toString() }; + const context = { userId: testUser?._id.toString() }; + + const result = (await getPledgesByUserId?.( + {}, + args, + context, + )) as InterfaceFundraisingCampaignPledges[]; + expect(result[0]._id).toEqual(testPledge?._id); + }); + + it("Returns all the pledges belonging to specific userId with where.name_contains", async () => { + const args = { + userId: testUser?._id.toString(), + where: { name_contains: testCampaign.name }, + }; + const context = { userId: testUser?._id.toString() }; + + const result = (await getPledgesByUserId?.( + {}, + args, + context, + )) as InterfaceFundraisingCampaignPledges[]; + const campaign = result[0].campaign as InterfaceFundraisingCampaign; + expect(campaign._id).toEqual(testCampaign._id); + }); + + it("Returns all the pledges belonging to specific userId with where.name_contains not equal", async () => { + const args = { + userId: testUser?._id.toString(), + where: { name_contains: testCampaign.name + "a" }, + }; + const context = { userId: testUser?._id.toString() }; + + const result = (await getPledgesByUserId?.( + {}, + args, + context, + )) as InterfaceFundraisingCampaignPledges[]; + expect(result.length).toEqual(0); + }); + + it("Returns all the pledges belonging to specific userId with where.firstName_contains", async () => { + const args = { + userId: testUser?._id.toString(), + where: { firstName_contains: testUser?.firstName }, + }; + const context = { userId: testUser?._id.toString() }; + + const result = (await getPledgesByUserId?.( + {}, + args, + context, + )) as InterfaceFundraisingCampaignPledges[]; + + const user = result[0].users[0] as InterfaceUser; + expect(user.firstName).toEqual(testUser?.firstName); + }); + + it("Returns all the pledges belonging to specific userId with where.firstName_contains not equal", async () => { + const args = { + userId: testUser?._id.toString(), + where: { firstName_contains: testUser?.firstName + "a" }, + }; + const context = { userId: testUser?._id.toString() }; + + const result = (await getPledgesByUserId?.( + {}, + args, + context, + )) as InterfaceFundraisingCampaignPledges[]; + expect(result.length).toEqual(0); + }); + + it("throws an error if the user does not have appUserProfile", async () => { + await AppUserProfile.deleteOne({ userId: testUser?._id }); + const args = { userId: testUser?._id.toString() }; + const context = { userId: testUser?._id.toString() }; + + try { + await getPledgesByUserId?.({}, args, context); + } catch (error: unknown) { + expect((error as Error).message).toEqual( + USER_NOT_AUTHORIZED_ERROR.MESSAGE, + ); + } + }); +}); diff --git a/tests/resolvers/Query/getUserTag.spec.ts b/tests/resolvers/Query/getUserTag.spec.ts new file mode 100644 index 0000000000..f81f51d960 --- /dev/null +++ b/tests/resolvers/Query/getUserTag.spec.ts @@ -0,0 +1,56 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { connect, disconnect } from "../../helpers/db"; + +import { getUserTag as getUserTagResolver } from "../../../src/resolvers/Query/getUserTag"; +import type { QueryGetUserTagArgs } from "../../../src/types/generatedGraphQLTypes"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import { createRootTagWithOrg, type TestUserTagType } from "../../helpers/tags"; +import { TAG_NOT_FOUND } from "../../../src/constants"; + +let MONGOOSE_INSTANCE: typeof mongoose; + +let testTag: TestUserTagType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + [, , testTag] = await createRootTagWithOrg(); +}); + +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: QueryGetUserTagArgs = { + id: new Types.ObjectId().toString(), + }; + + await getUserTagResolver?.({}, 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 tag with _id === args.id`, async () => { + const args: QueryGetUserTagArgs = { + id: testTag?._id.toString() ?? "", + }; + + const getUserTagAncestorsPayload = await getUserTagResolver?.({}, args, {}); + + expect(getUserTagAncestorsPayload).toEqual(testTag); + }); +}); diff --git a/tests/resolvers/Query/getUserTagAncestors.spec.ts b/tests/resolvers/Query/getUserTagAncestors.spec.ts new file mode 100644 index 0000000000..2a3ee00143 --- /dev/null +++ b/tests/resolvers/Query/getUserTagAncestors.spec.ts @@ -0,0 +1,64 @@ +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/groupChatById.spec.ts b/tests/resolvers/Query/groupChatById.spec.ts index 770557a301..2440387c61 100644 --- a/tests/resolvers/Query/groupChatById.spec.ts +++ b/tests/resolvers/Query/groupChatById.spec.ts @@ -42,7 +42,7 @@ describe("resolvers -> Query -> directChatsById", () => { it(`returns list of all directChats with directChat.users containing the user with _id === args.id`, async () => { const args: QueryGroupChatsByUserIdArgs = { - id: testGroupChat?._id, + id: testGroupChat?._id.toString() ?? "defaultString", }; const directChatsByUserIdPayload = await groupChatByIdResolver?.( diff --git a/tests/resolvers/Query/helperFunctions/getSort.spec.ts b/tests/resolvers/Query/helperFunctions/getSort.spec.ts index e37476b1f4..10b0b1668c 100644 --- a/tests/resolvers/Query/helperFunctions/getSort.spec.ts +++ b/tests/resolvers/Query/helperFunctions/getSort.spec.ts @@ -59,6 +59,8 @@ describe("getSort function", () => { ["commentCount_DESC", { commentCount: -1 }], ["fundingGoal_ASC", { fundingGoal: 1 }], ["fundingGoal_DESC", { fundingGoal: -1 }], + ["dueDate_ASC", { dueDate: 1 }], + ["dueDate_DESC", { dueDate: -1 }], ]; it.each(testCases)( diff --git a/tests/resolvers/Query/helperFunctions/getWhere.spec.ts b/tests/resolvers/Query/helperFunctions/getWhere.spec.ts index 908f6d611e..3c3494c9b6 100644 --- a/tests/resolvers/Query/helperFunctions/getWhere.spec.ts +++ b/tests/resolvers/Query/helperFunctions/getWhere.spec.ts @@ -10,6 +10,9 @@ import type { UserWhereInput, VenueWhereInput, CampaignWhereInput, + EventVolunteerGroupWhereInput, + PledgeWhereInput, + ActionItemCategoryWhereInput, } from "../../../../src/types/generatedGraphQLTypes"; describe("getWhere function", () => { @@ -17,14 +20,17 @@ describe("getWhere function", () => { string, Partial< EventWhereInput & + EventVolunteerGroupWhereInput & OrganizationWhereInput & PostWhereInput & UserWhereInput & DonationWhereInput & ActionItemWhereInput & + ActionItemCategoryWhereInput & FundWhereInput & CampaignWhereInput & - VenueWhereInput + VenueWhereInput & + PledgeWhereInput >, Record, ][] = [ @@ -102,9 +108,10 @@ describe("getWhere function", () => { { actionItemCategory_id: "6f43d" }, { actionItemCategoryId: "6f43d" }, ], - ["is_active", { is_active: true }, { isCompleted: false }], ["is_completed", { is_completed: true }, { isCompleted: true }], + ["is_completed", { is_completed: false }, { isCompleted: false }], ["event_id", { event_id: "6f43d" }, { eventId: "6f43d" }], + ["eventId", { eventId: "6f43d" }, { eventId: "6f43d" }], ["location", { location: "test location" }, { location: "test location" }], [ "location_not", @@ -321,11 +328,29 @@ describe("getWhere function", () => { { name_contains: "Test" }, { name: { $regex: "Test", $options: "i" } }, ], + ["fundId", { fundId: "6f6c" }, { fundId: "6f6c" }], + [ + "organizationId", + { organizationId: "6f6cd" }, + { 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 }], ]; it.each(testCases)( "should return correct where payload for %s", - (name, input, expected) => { + (_name, input, expected) => { const result = getWhere(input); expect(result).toEqual(expected); }, diff --git a/tests/resolvers/Query/me.spec.ts b/tests/resolvers/Query/me.spec.ts index 6e1d31aee4..9e3a334175 100644 --- a/tests/resolvers/Query/me.spec.ts +++ b/tests/resolvers/Query/me.spec.ts @@ -13,6 +13,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { createTestEvent } from "../../helpers/events"; import type { TestUserType } from "../../helpers/userAndOrg"; import { deleteUserFromCache } from "../../../src/services/UserCache/deleteUserFromCache"; +import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; let MONGOOSE_INSTANCE: typeof mongoose; let testUser: TestUserType; @@ -21,6 +22,10 @@ beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); testUser = (await createTestEvent())[0]; await deleteUserFromCache(testUser?._id); + const pledges = await FundraisingCampaignPledge.find({ + _id: new Types.ObjectId(), + }).lean(); + console.log(pledges); }); afterAll(async () => { @@ -53,10 +58,8 @@ describe("resolvers -> Query -> me", () => { _id: testUser?._id, }) .select(["-password"]) - .populate("joinedOrganizations") .populate("registeredEvents") - .lean(); expect(mePayload?.user).toEqual(user); diff --git a/tests/resolvers/Query/organizationsMemberConnection.spec.ts b/tests/resolvers/Query/organizationsMemberConnection.spec.ts index 2ce491dc82..0a773fcda5 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, @@ -319,6 +320,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, @@ -410,6 +412,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, @@ -504,6 +507,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, @@ -598,6 +602,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, @@ -680,6 +685,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, @@ -861,6 +867,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, @@ -932,6 +939,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, @@ -1032,6 +1040,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, @@ -1113,6 +1122,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, diff --git a/tests/resolvers/Query/user.spec.ts b/tests/resolvers/Query/user.spec.ts index 183260bd26..3d00aebbc6 100644 --- a/tests/resolvers/Query/user.spec.ts +++ b/tests/resolvers/Query/user.spec.ts @@ -11,6 +11,7 @@ import { deleteUserFromCache } from "../../../src/services/UserCache/deleteUserF import type { QueryUserArgs } from "../../../src/types/generatedGraphQLTypes"; import type { TestUserType } from "../../helpers/userAndOrg"; import { createTestUserAndOrganization } from "../../helpers/userAndOrg"; +import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; let MONGOOSE_INSTANCE: typeof mongoose; let testUser: TestUserType; @@ -19,6 +20,10 @@ beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); testUser = (await createTestUserAndOrganization())[0]; await deleteUserFromCache(testUser?.id); + const pledges = await FundraisingCampaignPledge.find({ + _id: new Types.ObjectId(), + }).lean(); + console.log(pledges); }); afterAll(async () => { diff --git a/tests/resolvers/Query/users.spec.ts b/tests/resolvers/Query/users.spec.ts index fc6003801c..fe51c3a55f 100644 --- a/tests/resolvers/Query/users.spec.ts +++ b/tests/resolvers/Query/users.spec.ts @@ -10,6 +10,7 @@ import { users as usersResolver } from "../../../src/resolvers/Query/users"; import type { QueryUsersArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; import { createTestUser } from "../../helpers/user"; +import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; let testUsers: (InterfaceUser & Document)[]; @@ -17,6 +18,10 @@ let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); + const pledges = await FundraisingCampaignPledge.find({ + _id: new mongoose.Types.ObjectId(), + }).lean(); + console.log(pledges); }); afterAll(async () => { diff --git a/tests/resolvers/Query/usersConnection.spec.ts b/tests/resolvers/Query/usersConnection.spec.ts index b58165d06d..cab2b92107 100644 --- a/tests/resolvers/Query/usersConnection.spec.ts +++ b/tests/resolvers/Query/usersConnection.spec.ts @@ -12,6 +12,8 @@ import { createTestUser, createTestUserAndOrganization, } from "../../helpers/userAndOrg"; +import { FundraisingCampaignPledge } from "../../../src/models/FundraisingCampaignPledge"; +import { Types } from "mongoose"; let MONGOOSE_INSTANCE: typeof mongoose; let testUsers: TestUserType[]; @@ -25,6 +27,10 @@ beforeAll(async () => { testOrganization?._id, true, ); + const pledges = await FundraisingCampaignPledge.find({ + _id: new Types.ObjectId(), + }).lean(); + console.log(pledges); }); afterAll(async () => { diff --git a/tests/resolvers/User/user.spec.ts b/tests/resolvers/User/user.spec.ts new file mode 100644 index 0000000000..02497663e6 --- /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/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.default.spec.ts b/tests/setup/minio/getMinioBinaryUrl/getMinioBinaryUrl.default.spec.ts new file mode 100644 index 0000000000..1781f90863 --- /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 0000000000..8f4fdc12f7 --- /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 0000000000..ad16f5b5da --- /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 0000000000..ba49a3b8fe --- /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 0000000000..f189d85c49 --- /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 0000000000..a94158c215 --- /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 0000000000..02d6bd4911 --- /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 0000000000..c35c3ffa3d --- /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 0000000000..9d59486cc3 --- /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 0000000000..88efcb8baf --- /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 0000000000..d1a530f1db --- /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 0000000000..664af46293 --- /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 0000000000..e76dda2f7d --- /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 0000000000..b2b72b487a --- /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 0000000000..a11ad08f56 --- /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 0000000000..9dd298e7d5 --- /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 0000000000..dd17e94d00 --- /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/vite.config.mts b/vite.config.mts index 77003926e7..12749c1ce5 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -10,7 +10,6 @@ export default defineConfig({ // specifically looks for those functions in the `globalSetup.ts` file. // More info here https://vitest.dev/config/#globalsetup globalSetup: ["./tests/helpers/globalSetup.ts"], - coverage: { // This tells vitest to include all files from ./src in test coverage. all: true, @@ -51,5 +50,11 @@ export default defineConfig({ // Tells vitest the time limit for an individual test block run. testTimeout: 30000, + + // Use a thread pool for parallel execution to improve performance + pool: 'threads', + + // Disable file-level parallelism to process files sequentially + fileParallelism: false, }, -}); +}); \ No newline at end of file