diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..364ed84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +venv_bankapp +dist +dist-ssr +*.local +.DS_Store + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.env + +__pycache__ + +docker-hub.sh +dockers_images_update.sh + +# martianbank/templates/configmap.yaml diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..169e7d9 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,79 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [oss-conduct@cisco.com][conduct-email]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +[conduct-email]: mailto:oss-conduct@cisco.com + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..258a80a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,69 @@ +# How to Contribute + +Thanks for your interest in contributing to Martian Bank! Here are a few general guidelines on contributing and +reporting bugs that we ask you to review. Following these guidelines helps to communicate that you respect the time of +the contributors managing and developing this open source project. In return, they should reciprocate that respect in +addressing your issue, assessing changes, and helping you finalize your pull requests. In that spirit of mutual respect, +we endeavor to review incoming issues and pull requests within 10 days, and will close any lingering issues or pull +requests after 60 days of inactivity. + +Please note that all of your interactions in the project are subject to our [Code of Conduct](/CODE_OF_CONDUCT.md). This +includes creation of issues or pull requests, commenting on issues or pull requests, and extends to all interactions in +any real-time space e.g., Slack, Discord, etc. + +## Table Of Contents + +- [Reporting Issues](#reporting-issues) +- [Development](#development) + +- [How to Contribute](#how-to-contribute) + - [Table Of Contents](#table-of-contents) + - [Reporting Issues](#reporting-issues) + - [Development](#development) + - [Sending Pull Requests](#sending-pull-requests) + - [Other Ways to Contribute](#other-ways-to-contribute) + +## Reporting Issues + +Before reporting a new issue, please ensure that the issue was not already reported or fixed by searching through our +[issues list](https://github.com/warisgill/bankapp/issues). + +When creating a new issue, please be sure to include a **title and clear description**, as much relevant information as +possible, and, if possible, a test case. + +**If you discover a security bug, please do not report it through GitHub. Instead, please see security procedures in +[SECURITY.md](/SECURITY.md).** + +## Development + +Follow the installation steps in [README.md](/readme.md) to install the application. + +## Sending Pull Requests + +Before sending a new pull request, take a look at existing pull requests and issues to see if the proposed change or fix +has been discussed in the past, or if the change was already implemented but not yet released. + +We expect new pull requests to include tests for any affected behavior, and, as we follow semantic versioning, we may +reserve breaking changes until the next major version release. + +## Other Ways to Contribute + +We welcome anyone that wants to contribute to MartianBank to triage and reply to open issues to help troubleshoot +and fix existing bugs. Here is what you can do: + +- Help ensure that existing issues follows the recommendations from the _[Reporting Issues](#reporting-issues)_ section, + providing feedback to the issue's author on what might be missing. + +- Review existing pull requests, and testing patches against real existing applications that use MartianBank. +- Write a test, or add a missing test case to an existing test. + +Thanks again for your interest on contributing to MartianBank! + +:heart: \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8af6a3c --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Cisco Systems, Inc. and its affiliates + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2998246 --- /dev/null +++ b/README.md @@ -0,0 +1,452 @@ + + +# MARTIAN BANK + +
+ +MartianBank is a microservices application that allows customers to access and manage their bank accounts, perform financial transactions, locate ATMs, and apply for loans. It is built using [React](https://react.dev/),[ Node.js](https://nodejs.org/en/about), [Python](https://flask.palletsprojects.com/en/2.3.x/) and is packaged in [Docker](https://www.docker.com/) containers. + + + +
+ +# Table of Contents +- [Application Design](#application-design) +- [Getting Started](#installation) + - [Installation on Docker Desktop Kubernetes](#1-installation-on-docker-desktop-kubernetes) + - [Installation on Minikube Cluster](#2-installation-on-minikube-cluster) + - [Installation on KIND Cluster](#3-installatioin-on-kind-cluster) + - [Installation on AWS EKS Cluster](#4-installation-on-aws-eks-cluster) + - [Running Locally](#5-local-installation) +- [Highlights](#highlights) +- [Roadmap](#roadmap) +- [Contributing](#contributing) +- [License](#license) + + +
+ +# Application Design + +The Martian Bank UI is created using [React](https://react.dev/) and [react-redux toolkit](https://redux-toolkit.js.org/). There is an [NGINX](https://www.nginx.com/) container that acts as a reverse proxy for UI and backend services. There are 6 microservices, out of which 2 (customer-auth and atm-locator) are developed in Node.js whereas the others are done using Flask (Python). The dashboard microservice talks to accounts, transactions and loan microservices using [gRPC](https://grpc.io/) and [http](https://en.wikipedia.org/wiki/HTTP) (can be flipped and is available as a deployment parameter). + +![Architecture Diagram](https://drive.google.com/uc?export=view&id=1H1MKRLmcuIKKyXgcX7VRkL18ECJDyw3q) + +
+ +# Installation +There are mulitple ways to install MartianBank. + +
+ +## 1. Installation on Docker Desktop Kubernetes +This tutorial will guide you through the process of setting up and installing the MartianBank app using Helm on a Kubernetes cluster. Helm is a package manager for Kubernetes that simplifies the deployment of applications and services. + +Before we begin, please ensure you have the following prerequisites installed: + +1. **Docker**: To run Kubernetes and containerized applications. You can download and install Docker from [here](https://www.docker.com/). +2. **Kubernetes**: Enable Kubernetes within Docker Desktop. Follow the instructions provided in the official Docker documentation [here](https://docs.docker.com/desktop/kubernetes/). +3. **kubectl**: The Kubernetes command-line tool to interact with the cluster. Install it using the guidelines found [here](https://kubernetes.io/docs/tasks/tools/). +4. **Helm**: The package manager for Kubernetes. Helm allows you to define, install, and upgrade complex Kubernetes applications. Follow the installation instructions [here](https://helm.sh/docs/intro/install/). + +Once you have the prerequisites installed, proceed with the following steps to set up MartianBank: + +#### Step 1: Clone the MartianBank GitHub Repository +First, download the MartianBank GitHub repository using the following steps: + +1. Open your terminal or command prompt. + +2. Clone the repository by running the command: +```bash +git clone https://github.com/warisgill/bankapp +``` + +3. Change to the downloaded repository directory using the command: +```bash +cd bankapp +``` + +4. Add the `configmap.yaml` config file to `martianbank/templates/` folder with the following content +``` yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-martianbank +data: + DB_URL: # enter the DB url + JWT_SECRET: # enter any string that you wish to use as a secret +``` + +When running Docker or pods, use `"mongodb://root:example@mongodb:27017"` for the local database. + +#### Step 2: Install MartianBank using Helm +Now that you have the MartianBank repository downloaded, you can use Helm to install the app on your Kubernetes cluster. + +1. To install MartianBank, use the Helm command: +```bash +helm install martianbank martianbank +``` + +By default loan, transaction and accounts microservices will run with http protocol. To switch to gRPC type the following command: +```bash +helm install martianbank martianbank --set SERVICE_PROTOCOL=grpc +``` + +Additionally, you can flip between mongoDB local and mongoDB Atlas (cloud database instance). To switch to local mongo, use the following flag: +```bash +helm install martianbank martianbank --set "mongodb.enabled=false" +``` + +By default, we use NGINX for reverse-proxy. If you want to deploy without NGINX, use this flag: +```bash +helm install martianbank martianbank --set "nginx.enabled=false" +``` + +2. Wait for the installation to complete. Helm will deploy the necessary components to your Kubernetes cluster. + +#### Step 3: Check Pod Status +After the installation is complete, you can verify the status of the pods running within your Kubernetes cluster. + +To get a list of pods, run the following command: +```bash +kubectl get pods +``` + +#### Step 4: Get MartianBank App URL + +To access the MartianBank app, you need to find the URL (IP address) of the running MartianBank service. +Run the following command to get the list of services: +```bash +kubectl get service +``` + +Look for the **EXTERNAL-IP** under the **_nginx_** microservice. This IP address is where the MartianBank app is accessible. + +#### Step 5: Access MartianBank App +Now that you have the URL (IP address) of the MartianBank app, you can access it using a web browser. +1. Copy the URL (IP address) from the **EXTERNAL-IP** field. +2. Paste the URL in your browser's address bar and press Enter to access the MartianBank app. For example, if the IP is `localhost`, simply paste `localhost` in the browser to access the MartianBank app. + +Congratulations! You have successfully installed and accessed the MartianBank app on your Kubernetes cluster using Helm. Now you can explore and use the app as needed. If you encounter any issues during installation, double-check the prerequisites and ensure that all steps were followed correctly. Happy banking with MartianBank! + +#### Tutorial: Uninstalling the MartianBank App +In this section, you'll learn how to uninstall the MartianBank app from your Kubernetes cluster. We'll follow two simple steps to ensure a clean removal of the app. + +**Step 1: Uninstall using Helm** +To remove the MartianBank app from the cluster, we'll use Helm to uninstall it. + +1. Open your terminal or command prompt. + +2. Run the following Helm command to uninstall the MartianBank release: +```bash +helm uninstall martianbank +``` +This command will remove all the Kubernetes resources associated with the MartianBank release. + + +**Step 2: Delete Remaining Resources** +Although Helm has uninstalled the main components, there might still be some resources remaining in the cluster. To ensure a complete removal, we'll use kubectl to delete all resources in the default namespace. + +1. Run the following kubectl command: +```bash +kubectl delete all --all --namespace default +``` + +This command will delete all resources (pods, services, deployments, etc.) in the default namespace. + +After performing the above two steps, the MartianBank app should be completely uninstalled from your Kubernetes cluster. + + +**Note:** If you have installed the MartianBank app in a namespace other than "default," make sure to change the `--namespace` flag in both the Helm and kubectl commands accordingly. + +Now you have successfully uninstalled the MartianBank app from your Kubernetes cluster, and all associated resources have been removed. If you have any other Helm releases or resources running on the cluster, you can manage them similarly using Helm commands and kubectl operations. + +
+ +## 2. Installation on Minikube Cluster + +Let's go through the steps to install MartianBank using Minikube. Minikube allows you to run a single-node Kubernetes cluster on your local machine, making it ideal for testing and development purposes. + +**Step 1: Install Minikube and Prerequisites** + +If you haven't already installed Minikube and its prerequisites, follow the official Minikube installation guide based on your operating system: [Minikube Installation Guide](https://minikube.sigs.k8s.io/docs/start/) + +**Step 2: Start Minikube Cluster** + +1. Open your terminal or command prompt. + +2. Start the Minikube cluster by running the following command: +```bash +minikube start +``` + +This command will create and start a local Kubernetes cluster using Minikube. + +**Step 3: Install Helm** + +Before installing MartianBank, you need to have Helm installed on your local machine. + +- Follow the official Helm installation guide based on your operating system: [Helm Installation Guide](https://helm.sh/docs/intro/install/) + + +**Step 4: Clone the MartianBank GitHub Repository** + +1. Open your terminal or command prompt. + +2. Clone the MartianBank GitHub repository and navigate to the downloaded directory as mentioned in the previous tutorial. +```bash +git clone https://github.com/warisgill/bankapp +cd bankapp +``` + +**Step 5: Install MartianBank using Helm** + +Now that you have Minikube running and Helm installed, you can proceed with installing MartianBank on your Minikube cluster. + +1. To install MartianBank, use the Helm command: +```bash +helm install martianbank martianbank +``` + +Wait for the installation to complete. Helm will deploy the necessary components to your Minikube cluster. + +**Step 6: Use Minikube Tunnel** + +After installing MartianBank on your Minikube cluster, you may encounter that the command `kubectl get service` displays the services with an "external IP" in the "pending" state. This happens because Minikube does not natively support LoadBalancer type services with external IPs. However, you can use the `minikube tunnel` command to enable external access to LoadBalancer services. + +To make the LoadBalancer type service accessible via an external IP in Minikube, you can use the `minikube tunnel` command. This command sets up a network route to expose the LoadBalancer's IP externally. Here's how to use it: Now, to make the LoadBalancer accessible from an external IP, run the following command in a **_new terminal_**: +```bash +minikube tunnel +``` + +The `minikube tunnel` command will create a network route to expose the LoadBalancer service to an external IP address. The external IP should no longer be in the "pending" state after running this command. + +**Step 7: Access the MartianBank App** + +After running `minikube tunnel`, the LoadBalancer's external IP should be available. You can get the IP by running: +```bash +kubectl get service +``` +Look for the external IP in the output (for nginx pod). Copy the IP address and paste it into your browser's address bar. You should be able to access the MartianBank app. + +**Step 8: Stop Minikube Tunnel** +Remember that the `minikube tunnel` command will continue running in your terminal until you stop it manually. When you're done testing the app, you can stop the tunnel by pressing `Ctrl + C` in the terminal where the `minikube tunnel` command is running. + +That's it! Using `minikube tunnel`, you can expose LoadBalancer services in Minikube and access them via external IP addresses for testing and development. + + +**Step 9: Uninstall MartianBank** +If you want to uninstall MartianBank from your Minikube cluster, follow the same uninstallation steps mentioned in the previous tutorial: +```bash +helm uninstall martianbank +kubectl delete all --all --namespace default +``` + +That's it! You now have MartianBank installed and running on your Minikube cluster. Minikube provides an easy way to test and develop applications locally on a Kubernetes cluster. Happy testing with MartianBank on your Minikube setup! + +
+ +## 3. Installatioin on Kind Cluster + +**`Warning:`** _Kind Cluster, by default, does not support the LoadBalancer type service with external IPs [[Load balancer external-ip pending · Issue #411 · kubernetes-sigs/kind (github.com)](https://github.com/kubernetes-sigs/kind/issues/411)]. This means that if you want to access services using an external IP in the Kind cluster, you will need to use an alternative approach. If you does not know how to resolove it, please follow the minikube or docker desktop kubernetes tutorials for installation setup._ + +Setting up MartianBank on a KIND (Kubernetes in Docker) cluster involves a few additional steps compared to a regular Kubernetes cluster. KIND allows you to create a lightweight Kubernetes cluster inside Docker containers, which is ideal for testing and development purposes. Here's how you can set up MartianBank on a KIND cluster: + +**Step 1: Install KIND and Docker** +If you haven't already installed KIND and Docker, you need to do that first. Follow the official installation guides for [KIND](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) and [Docker](https://docs.docker.com/get-docker/) based on your operating system. + +**Step 2: Create a KIND Cluster** + +1. Open your terminal or command prompt. + +2. Create a KIND cluster by running the following command: +```bash +kind create cluster --name martianbank +``` + +This will create a new KIND cluster named "martianbank" with a single Kubernetes node. + +**Step 3: Configure Kubectl** + +The KIND cluster should now be running, but your `kubectl` is not automatically configured to communicate with the cluster. You need to set the context for your `kubectl` to use the KIND cluster. + +1. Run the following command to set the `kubectl` context to the new KIND cluster: +```bash +kubectl cluster-info --context kind-martianbank +``` + +**Step 4: Install MartianBank using Helm** + +Now that your KIND cluster is set up and Helm is installed, you can proceed with installing MartianBank. + +1. Clone the MartianBank GitHub repository and navigate to the downloaded directory as mentioned in the previous tutorial. + +2. Install MartianBank using the Helm command: +```bash +helm install martianbank martianbank +``` + +**Step 5: Access MartianBank App** +After the installation is complete, you can access the MartianBank app just like before. Find the IP address of the running MartianBank service using `kubectl get service` and access it in your browser (previous tutorial). + + +**Step 6: Uninstall MartianBank** +If you want to uninstall MartianBank from the KIND cluster, follow the same uninstallation steps mentioned in the previous tutorial: +```bash +helm uninstall martianbank +kubectl delete all --all --namespace default +``` + +That's it! You now have MartianBank installed and running on your KIND cluster. Remember that KIND clusters are ephemeral and will be destroyed once you delete them. You can always create a new cluster with the same name or a different one using `kind create cluster` if needed. Happy testing with MartianBank on your KIND cluster. + +
+ +## 4. Installation on AWS EKS cluster + +**Step 1: Create an EKS cluster on AWS:** + +1. Install AWS CLI tool and configure it (pass in access key, secret key, region, and it creates ~/.aws/config and ~/.aws/credentials files). +```shell +aws configure +``` + +2. Install eksctl tool +```shell +brew tap weaveworks/tap; brew install weaveworks/tap/eksctl +``` + +3. Install IAM authenticator +```shell +brew install aws-iam-authenticator +``` + +4. Create a cluster.yaml file anywhere on your system. +```yaml +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: + region: us-east-1 +vpc: + cidr: "172.20.0.0/16" ## Can change this value + nat: + gateway: Single + clusterEndpoints: + publicAccess: true + privateAccess: true +nodeGroups: + - name: ng-1 + minSize: 2 + maxSize: 2 + instancesDistribution: + maxPrice: 0.093 + instanceTypes: ["t3a.large", "t3.large"] + onDemandBaseCapacity: 0 + onDemandPercentageAboveBaseCapacity: 50 + spotInstancePools: 2 + ssh: + publicKeyPath: +``` + +5. Create an EKS cluster using this command (takes ~20 minutes) +```shell +eksctl create cluster -f cluster.yaml +``` + +**Step 2: Install MartianBank using Helm** + +Now that your EKS cluster is set up, you can proceed with installing MartianBank. + +1. Go to your cloned repository and install MartianBank using the Helm command: +```shell +helm install martianbank martianbank +``` + +By default loan, transaction and accounts microservices will run with http protocol. To switch to gRPC type the following command: +```bash +helm install martianbank martianbank --set SERVICE_PROTOCOL=grpc +``` + +Additionally, you can flip between mongoDB local and mongoDB Atlas (cloud database instance). To switch to local mongo, use the following flag: +```bash +helm install martianbank martianbank --set "mongodb.enabled=false" +``` + +By default, we use NGINX for reverse-proxy. If you want to deploy without NGINX, use this flag: +```bash +helm install martianbank martianbank --set "nginx.enabled=false" +``` + +2. Verify that all pods are running using this command: +```shell +kubectl get pods +``` + +**Step 3: Access MartianBank App** + +After the installation is complete, you can access the MartianBank app by finding the IP address of the running MartianBank service using `kubectl get service` and access it in your browser. + + +**Step 4: Uninstall MartianBank** + +If you want to uninstall MartianBank from the EKS cluster, follow these uninstallation steps: +```shell +helm uninstall martianbank +kubectl delete all --all --namespace default +``` + +
+ +## 5. Local Installation + +**Option 1: Running on localhost** + +Before you start the installation, ensure that you have `.env` files setup inside all the microservices. + +1. To run all the microservices and UI: +```bash +cd scripts +bash run_local.sh +``` + +Fire up `http://localhost:3000` to access the Martian Bank App. + +2. To stop all the microservices: +```bash +cd scripts +bash stop_local.sh +``` + +
+ +# Hightlights + +- Micro Services Architecture ​ +- Helm based configurable deployments options​ (eg switching between HTTP and GRPC)​ +- Docker, Kind and EKS based easy installations​ +- Swagger APIs and comprehensive documentation​ +- Performance tests and load generation capabilities​ +- ETI lab deployer with Panoptica​ +- Opensource + +
+ +# Roadmap + +- Images for background, coin, card, and map. +- Swagger docs for remaining endpoints +- Blog posts and videos to support contributors + +
+ +# Contributing +Pull requests and bug reports are welcome. For larger changes please create an Issue in GitHub first to discuss your proposed changes and possible implications. + +More more details please see the [Contribution guidelines for this project](CONTRIBUTING.md) + +
+ +# License + +[BSD 3-Clause License](https://opensource.org/license/bsd-3-clause/) + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5ca80db --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,39 @@ +# Security Policies and Procedures + +This document outlines security procedures and general policies for the +MartianBank project. + +- [Reporting a Bug](#reporting-a-bug) +- [Disclosure Policy](#disclosure-policy) +- [Comments on this Policy](#comments-on-this-policy) + +## Reporting a Bug + +The MartianBank team and community take all security bugs in +MartianBank seriously. Thank you for improving the security of +MartianBank. We appreciate your efforts and responsible disclosure and +will make every effort to acknowledge your contributions. + +Report security bugs by emailing `oss-security@cisco.com`. + +The lead maintainer will acknowledge your email within 48 hours, and will send a +more detailed response within 48 hours indicating the next steps in handling +your report. After the initial reply to your report, the security team will +endeavor to keep you informed of the progress towards a fix and full +announcement, and may ask for additional information or guidance. + +## Disclosure Policy + +When the security team receives a security bug report, they will assign it to a +primary handler. This person will coordinate the fix and release process, +involving the following steps: + +- Confirm the problem and determine the affected versions. +- Audit code to find any potential similar problems. +- Prepare fixes for all releases still under maintenance. These fixes will be + released as quickly as possible. + +## Comments on this Policy + +If you have suggestions on how this process could be improved please submit a +pull request. \ No newline at end of file diff --git a/accounts/Dockerfile b/accounts/Dockerfile new file mode 100644 index 0000000..dacfa80 --- /dev/null +++ b/accounts/Dockerfile @@ -0,0 +1,20 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 python + +RUN mkdir /service +COPY protobufs/ /service/protobufs/ +COPY accounts/ /service/accounts/ +WORKDIR /service/accounts +RUN python -m pip install --upgrade pip +RUN python -m pip install -r requirements.txt + + +RUN python -m grpc_tools.protoc -I ../protobufs --python_out=. --grpc_python_out=. ../protobufs/accounts.proto + + +EXPOSE 50051 + +ENTRYPOINT ["python", "accounts.py"] \ No newline at end of file diff --git a/accounts/accounts.py b/accounts/accounts.py new file mode 100644 index 0000000..2d27337 --- /dev/null +++ b/accounts/accounts.py @@ -0,0 +1,233 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from concurrent import futures +import random +import datetime +import os +import grpc +from accounts_pb2 import * +import accounts_pb2_grpc +import logging +from dotmap import DotMap +from pymongo.mongo_client import MongoClient +from flask import Flask, request, jsonify +# set logging to debug +logging.basicConfig(level=logging.DEBUG) + +from dotenv import load_dotenv +load_dotenv() + +# db_host = os.getenv("DATABASE_HOST", "localhost") +db_url = os.getenv("DB_URL") +if db_url is None: + raise Exception("DB_URL environment variable is not set") + + +# protocol = os.getenv('SERVICE_PROTOCOL') +protocol = os.getenv('SERVICE_PROTOCOL', 'http') +if protocol is None: + raise Exception("SERVICE_PROTOCOL environment variable is not set") + +protocol = protocol.lower() +logging.debug(f"microservice protocol: {protocol}") + + +uri = db_url + +client = MongoClient(uri) +db = client["bank"] +collection = db["accounts"] + + +class AccountsGeneric: + def getAccountDetails(self, request): + logging.debug("Get Account Details called") + account = collection.find_one({"account_number": request.account_number}) + + + + if account: + return {'account_number': account["account_number"],'name': account["name"], 'balance': account["balance"], 'currency': account["currency"]} + + + return {} + + # Todo: check if the account already exist or not + + def createAccount(self, request): + logging.debug("Create Account called") + # find the account with email and account type if it already exist then return false + count = collection.count_documents( + {"email_id": request.email_id, "account_type": request.account_type} + ) + + logging.debug(f" count: {count}") + + if count > 0: + logging.debug("Account already exist") + return False # CreateAccountResponse(result=False) + + account = { + "email_id": request.email_id, + "account_type": request.account_type, + "address": request.address, + "govt_id_number": request.govt_id_number, + "government_id_type": request.government_id_type, + # "account_holder_name": request.account_holder_name, + "name": request.name, + "balance": 100, + "currency": "USD", + } + + # assign a random 16 digit account number + account[ + "account_number" + ] = f"IBAN{random.randint(1000000000000000, 9999999999999999)}" + # timestamp the account creation + account["created_at"] = datetime.datetime.now() + # insert the account into the list of accounts + collection.insert_one(account) + return True # CreateAccountResponse(result=True) + + def getAccounts(self, request): + email_id = request.email_id + accounts = collection.find({"email_id": email_id}) + account_list = [] + for account in accounts: + # logging.debug(account["balance"]) + # account_list.append( + # Account( + # account_number=account["account_number"], + # email_id=account["email_id"], + # account_type=account["account_type"], + # address=account["address"], + # govt_id_number=account["govt_id_number"], + # government_id_type=account["government_id_type"], + # name=account["name"], + # balance=account["balance"], + # currency=account["currency"], + # ) + # ) + acc = { + k: v + for k, v in account.items() + if k + in [ + "account_number", + "email_id", + "account_type", + "address", + "govt_id_number", + "government_id_type", + "name", + "balance", + "currency", + ] + } + account_list.append(acc) + + return account_list + + +class AccountDetailsService(accounts_pb2_grpc.AccountDetailsServiceServicer): + def __init__(self): + self.accounts = AccountsGeneric() + + def getAccountDetails(self, request, context): + + logging.debug("Get Account Details called") + + account = self.accounts.getAccountDetails(request) + + if len(account) > 0: + return AccountDetail( + account_number=account["account_number"], + name=account["name"], + balance=account["balance"], + currency=account["currency"], + ) + return AccountDetail() + + def createAccount(self, request, context): + # return self.accounts.createAccount(request) + result = self.accounts.createAccount(request) + return CreateAccountResponse(result=result) + + def getAccounts(self, request, context): + # return self.accounts.getAccounts(request) + accounts = self.accounts.getAccounts(request) + account_list = [] + for account in accounts: + account_list.append( + Account( + account_number=account["account_number"], + email_id=account["email_id"], + account_type=account["account_type"], + address=account["address"], + govt_id_number=account["govt_id_number"], + government_id_type=account["government_id_type"], + name=account["name"], + balance=account["balance"], + currency=account["currency"], + ) + ) + return GetAccountsResponse(accounts=account_list) + + +app = Flask(__name__) +accounts_generic = AccountsGeneric() +@app.route("/account-detail", methods=["POST"]) +def getAccountDetails(): + data = request.json + data = DotMap(data) + # account_number = request.json["account_number"] + account = accounts_generic.getAccountDetails(data) + return jsonify(account) + +@app.route("/create-account", methods=["POST"]) +def createAccount(): + data = request.json + data = DotMap(data) + result = accounts_generic.createAccount(data) + return jsonify(result) + +@app.route("/get-all-accounts", methods=["POST"]) +def getAccounts(): + data = request.json + data = DotMap(data) + accounts = accounts_generic.getAccounts(data) + return jsonify(accounts) + + + +def serverFlask(port): + logging.debug(f"Starting Flask server on port {port}") + app.run(host='0.0.0.0' ,port=port, debug=True) + + +def serverGRPC(port): + # recommendations_host = os.getenv("RECOMMENDATIONS_HOST", "localhost") + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + accounts_pb2_grpc.add_AccountDetailsServiceServicer_to_server( + AccountDetailsService(), server + ) + server.add_insecure_port(f"[::]:{port}") + # server.add_insecure_port(f"{recommendations_host}:50051") + # print server ip and port + logging.debug(f"Server started at port {port}") + # print IP + server.start() + server.wait_for_termination() + + + + +if __name__ == "__main__": + port = 50051 + # serverGRPC(port) + if protocol == "grpc": + serverGRPC(port) + else: + serverFlask(port) diff --git a/accounts/accounts_pb2.py b/accounts/accounts_pb2.py new file mode 100644 index 0000000..ac6ba3e --- /dev/null +++ b/accounts/accounts_pb2.py @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: accounts.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x61\x63\x63ounts.proto\"\xbf\x01\n\x07\x41\x63\x63ount\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\x12\x10\n\x08\x65mail_id\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x03 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x04 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x05 \x01(\t\x12\x1a\n\x12government_id_type\x18\x06 \x01(\t\x12\x0c\n\x04name\x18\x07 \x01(\t\x12\x10\n\x08\x63urrency\x18\x08 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\t \x01(\x01\"\x91\x01\n\x14\x43reateAccountRequest\x12\x10\n\x08\x65mail_id\x18\x01 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x02 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x04 \x01(\t\x12\x1a\n\x12government_id_type\x18\x05 \x01(\t\x12\x0c\n\x04name\x18\x06 \x01(\t\"\'\n\x15\x43reateAccountResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"&\n\x12GetAccountsRequest\x12\x10\n\x08\x65mail_id\x18\x01 \x01(\t\"1\n\x13GetAccountsResponse\x12\x1a\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x08.Account\"X\n\rAccountDetail\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\x03 \x01(\x01\x12\x10\n\x08\x63urrency\x18\x04 \x01(\t\"1\n\x17GetAccountDetailRequest\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t2\xd0\x01\n\x15\x41\x63\x63ountDetailsService\x12=\n\x11getAccountDetails\x12\x18.GetAccountDetailRequest\x1a\x0e.AccountDetail\x12>\n\rcreateAccount\x12\x15.CreateAccountRequest\x1a\x16.CreateAccountResponse\x12\x38\n\x0bgetAccounts\x12\x13.GetAccountsRequest\x1a\x14.GetAccountsResponseb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'accounts_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _ACCOUNT._serialized_start=19 + _ACCOUNT._serialized_end=210 + _CREATEACCOUNTREQUEST._serialized_start=213 + _CREATEACCOUNTREQUEST._serialized_end=358 + _CREATEACCOUNTRESPONSE._serialized_start=360 + _CREATEACCOUNTRESPONSE._serialized_end=399 + _GETACCOUNTSREQUEST._serialized_start=401 + _GETACCOUNTSREQUEST._serialized_end=439 + _GETACCOUNTSRESPONSE._serialized_start=441 + _GETACCOUNTSRESPONSE._serialized_end=490 + _ACCOUNTDETAIL._serialized_start=492 + _ACCOUNTDETAIL._serialized_end=580 + _GETACCOUNTDETAILREQUEST._serialized_start=582 + _GETACCOUNTDETAILREQUEST._serialized_end=631 + _ACCOUNTDETAILSSERVICE._serialized_start=634 + _ACCOUNTDETAILSSERVICE._serialized_end=842 +# @@protoc_insertion_point(module_scope) diff --git a/accounts/accounts_pb2_grpc.py b/accounts/accounts_pb2_grpc.py new file mode 100644 index 0000000..6acacb6 --- /dev/null +++ b/accounts/accounts_pb2_grpc.py @@ -0,0 +1,148 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import accounts_pb2 as accounts__pb2 + + +class AccountDetailsServiceStub(object): + """message GetAccountDetailResponse { + AccountDetail account = 1; + } + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.getAccountDetails = channel.unary_unary( + '/AccountDetailsService/getAccountDetails', + request_serializer=accounts__pb2.GetAccountDetailRequest.SerializeToString, + response_deserializer=accounts__pb2.AccountDetail.FromString, + ) + self.createAccount = channel.unary_unary( + '/AccountDetailsService/createAccount', + request_serializer=accounts__pb2.CreateAccountRequest.SerializeToString, + response_deserializer=accounts__pb2.CreateAccountResponse.FromString, + ) + self.getAccounts = channel.unary_unary( + '/AccountDetailsService/getAccounts', + request_serializer=accounts__pb2.GetAccountsRequest.SerializeToString, + response_deserializer=accounts__pb2.GetAccountsResponse.FromString, + ) + + +class AccountDetailsServiceServicer(object): + """message GetAccountDetailResponse { + AccountDetail account = 1; + } + + """ + + def getAccountDetails(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def createAccount(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getAccounts(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AccountDetailsServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'getAccountDetails': grpc.unary_unary_rpc_method_handler( + servicer.getAccountDetails, + request_deserializer=accounts__pb2.GetAccountDetailRequest.FromString, + response_serializer=accounts__pb2.AccountDetail.SerializeToString, + ), + 'createAccount': grpc.unary_unary_rpc_method_handler( + servicer.createAccount, + request_deserializer=accounts__pb2.CreateAccountRequest.FromString, + response_serializer=accounts__pb2.CreateAccountResponse.SerializeToString, + ), + 'getAccounts': grpc.unary_unary_rpc_method_handler( + servicer.getAccounts, + request_deserializer=accounts__pb2.GetAccountsRequest.FromString, + response_serializer=accounts__pb2.GetAccountsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'AccountDetailsService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class AccountDetailsService(object): + """message GetAccountDetailResponse { + AccountDetail account = 1; + } + + """ + + @staticmethod + def getAccountDetails(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/AccountDetailsService/getAccountDetails', + accounts__pb2.GetAccountDetailRequest.SerializeToString, + accounts__pb2.AccountDetail.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def createAccount(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/AccountDetailsService/createAccount', + accounts__pb2.CreateAccountRequest.SerializeToString, + accounts__pb2.CreateAccountResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getAccounts(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/AccountDetailsService/getAccounts', + accounts__pb2.GetAccountsRequest.SerializeToString, + accounts__pb2.GetAccountsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/accounts/api_client.py b/accounts/api_client.py new file mode 100644 index 0000000..93598de --- /dev/null +++ b/accounts/api_client.py @@ -0,0 +1,14 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +import grpc +from accounts_pb2_grpc import * +from accounts_pb2 import * + +channel = grpc.insecure_channel('localhost:50051') +client = AccountDetailsServiceStub(channel) + +request = CreateAccountRequest(email_id="test", account_type="test", address="test", govt_id_number="test", government_id_type="test", name="test") +response = client.createAccount(request) +print(response) \ No newline at end of file diff --git a/accounts/requirements.txt b/accounts/requirements.txt new file mode 100644 index 0000000..31f0165 --- /dev/null +++ b/accounts/requirements.txt @@ -0,0 +1,23 @@ +attrs==23.1.0 +click==7.1.2 +dnspython==2.3.0 +Flask==1.1.4 +Flask-Cors==3.0.10 +grpcio==1.54.2 +grpcio-tools==1.54.2 +itsdangerous==1.1.0 +Jinja2==2.11.3 +MarkupSafe==2.0.1 +more-itertools==9.1.0 +packaging==23.1 +pluggy==0.13.1 +protobuf==4.23.2 +py==1.11.0 +pymongo==4.3.3 +pytest==5.4.3 +six==1.16.0 +wcwidth==0.2.6 +Werkzeug==1.0.1 +requests +dotmap +python-dotenv==1.0.0 \ No newline at end of file diff --git a/atm-locator/.dockerignore b/atm-locator/.dockerignore new file mode 100644 index 0000000..e69de29 diff --git a/atm-locator/Dockerfile b/atm-locator/Dockerfile new file mode 100644 index 0000000..ad4dd7f --- /dev/null +++ b/atm-locator/Dockerfile @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 node:14 +WORKDIR /usr/src/app +COPY ./package*.json /usr/src/app +RUN npm install +COPY . ./ +EXPOSE 8001 +CMD ["npm", "start"] \ No newline at end of file diff --git a/atm-locator/config/atm_data.json b/atm-locator/config/atm_data.json new file mode 100644 index 0000000..b958e05 --- /dev/null +++ b/atm-locator/config/atm_data.json @@ -0,0 +1,301 @@ +[ + { + "_id": { "$oid": "64a6f1cc8c1899820dbdf25a" }, + "name": "Martian ATM (Highway)", + "address": { + "street": "14th Street, Martian Way", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.775, "longitude": -81.188 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 2, + "isOpen": true, + "createdAt": { "$date": "2023-07-06T16:54:36.22Z" }, + "updatedAt": { "$date": "2023-07-06T16:54:36.22Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f2268c1899820dbdf25c" }, + "name": "Martian ATM (Claytor Lake)", + "address": { + "street": "234, Highlander Road", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.775, "longitude": -81.1875 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "Closed on weekends", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 1, + "isOpen": false, + "createdAt": { "$date": "2023-07-06T16:56:06.095Z" }, + "updatedAt": { "$date": "2023-07-06T16:56:06.095Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f2d18c1899820dbdf25e" }, + "name": "Martian ATM (Musk Center)", + "address": { + "street": "17th Musk Rd", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.765, "longitude": -81.1885 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 4, + "isOpen": true, + "createdAt": { "$date": "2023-07-06T16:58:57.179Z" }, + "updatedAt": { "$date": "2023-07-06T16:58:57.179Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f3398c1899820dbdf260" }, + "name": "Martian ATM (Rocket Launchpad)", + "address": { + "street": "Martian Rocker Launcher", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.77, "longitude": -81.1883 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "10:00 AM - 3:00 PM" + }, + "atmHours": "24 hours", + "numberOfATMs": 4, + "isOpen": true, + "createdAt": { "$date": "2023-07-06T17:00:41.542Z" }, + "updatedAt": { "$date": "2023-07-06T17:00:41.542Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f3838c1899820dbdf262" }, + "name": "Martian ATM (Courthouse)", + "address": { + "street": "Mars Courthouse", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.776, "longitude": -81.1923 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 3, + "isOpen": true, + "createdAt": { "$date": "2023-07-06T17:01:55.592Z" }, + "updatedAt": { "$date": "2023-07-06T17:01:55.592Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f3b58c1899820dbdf264" }, + "name": "Martian ATM (Theater)", + "address": { + "street": "23, National Theater", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.778, "longitude": -81.1926 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 2, + "isOpen": false, + "createdAt": { "$date": "2023-07-06T17:02:45.324Z" }, + "updatedAt": { "$date": "2023-07-06T17:02:45.324Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f40b8c1899820dbdf266" }, + "name": "Martian ATM (Oak Hill)", + "address": { + "street": "Rover Road", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.769, "longitude": -81.1897 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 3, + "isOpen": true, + "createdAt": { "$date": "2023-07-06T17:04:11.271Z" }, + "updatedAt": { "$date": "2023-07-06T17:04:11.271Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f4498c1899820dbdf268" }, + "name": "Martian ATM (Beckley Main)", + "address": { + "street": "511 Naivelle Road", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.767, "longitude": -81.1904 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 2, + "isOpen": true, + "createdAt": { "$date": "2023-07-06T17:05:13.355Z" }, + "updatedAt": { "$date": "2023-07-06T17:05:13.355Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f4a58c1899820dbdf26a" }, + "name": "Martian ATM (The Exchange)", + "address": { + "street": "470, 16th Street", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.765, "longitude": -81.1877 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "Closed on weekends", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 2, + "isOpen": true, + "createdAt": { "$date": "2023-07-06T17:06:45.481Z" }, + "updatedAt": { "$date": "2023-07-06T17:06:45.481Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64a6f4d28c1899820dbdf26c" }, + "name": "Martian ATM (The Flats)", + "address": { + "street": "471, 16th Street", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.764, "longitude": -81.1897 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "Closed on weekends", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 2, + "isOpen": false, + "createdAt": { "$date": "2023-07-06T17:07:30.883Z" }, + "updatedAt": { "$date": "2023-07-06T17:07:30.883Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64ac747deaa5c8310cb21a1b" }, + "name": "Martian ATM (The Mark)", + "address": { + "street": "630, 14th Street", + "city": "Musk City", + "state": "Mars", + "zip": "40411" + }, + "coordinates": { "latitude": 37.76, "longitude": -81.1877 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 2:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 1, + "isOpen": true, + "createdAt": { "$date": "2023-07-10T21:13:33.658Z" }, + "updatedAt": { "$date": "2023-07-10T21:13:33.658Z" }, + "__v": 0, + "interPlanetary": false + }, + { + "_id": { "$oid": "64b072fd6981fda9e346bdde" }, + "name": "Earthern ATM (Georgia Tech)", + "address": { + "street": "470, 16th Street", + "city": "Atlanta", + "state": "Georgia", + "zip": "30363" + }, + "coordinates": { "latitude": -94.764, "longitude": 31.1897 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 4, + "isOpen": true, + "createdAt": { "$date": "2023-07-13T21:56:13.652Z" }, + "updatedAt": { "$date": "2023-07-13T21:56:13.652Z" }, + "__v": 0, + "interPlanetary": true + }, + { + "_id": { "$oid": "64b0735f6981fda9e346bde0" }, + "name": "Saturn ATM (Mimas Moon)", + "address": { + "street": "Outer ring", + "city": "Mimas", + "state": "Saturn", + "zip": "100" + }, + "coordinates": { "latitude": -94.764, "longitude": 31.1897 }, + "timings": { + "monFri": "9:00 AM - 5:00 PM", + "satSun": "10:00 AM - 3:00 PM", + "holidays": "Closed on holidays" + }, + "atmHours": "24 hours", + "numberOfATMs": 3, + "isOpen": true, + "createdAt": { "$date": "2023-07-13T21:57:51.67Z" }, + "updatedAt": { "$date": "2023-07-13T21:57:51.67Z" }, + "__v": 0, + "interPlanetary": true + } +] diff --git a/atm-locator/config/db.js b/atm-locator/config/db.js new file mode 100644 index 0000000..584db26 --- /dev/null +++ b/atm-locator/config/db.js @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import mongoose from "mongoose"; +import ATM from "../models/atmModel.js"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +const connectDB = async () => { + try { + console.log( + ` --- Connecting to MongoDB for atm-locator microservice --- `.cyan + ); + + if (process.env.DATABASE_HOST) { + console.log( + `Connecting to local MongoDB at ${process.env.DATABASE_HOST} ...` + ); + const conn = await mongoose.connect( + `mongodb://${process.env.DATABASE_HOST}:27017/` + ); + + console.log(`Seeding database with data from atm_data.json ...`); + + const atmDataFile = join( + dirname(fileURLToPath(import.meta.url)), + "atm_data.json" + ); + const rawData = fs.readFileSync(atmDataFile); + const jsonData = JSON.parse(rawData); + const processedData = jsonData.map((item) => ({ + ...item, + _id: new mongoose.Types.ObjectId(item._id.$oid), + createdAt: new Date(item.createdAt.$date), + updatedAt: new Date(item.updatedAt.$date), + })); + try { + try { + await ATM.collection.drop(); + } + catch (error) { + console.log(`Error: ${error.message}`.red.bold); + } + await ATM.insertMany(processedData); + console.log(`Database seeded with ${processedData.length} records.`); + } catch (error) { + console.log(`Error: ${error.message}`.red.bold); + } + } else { + console.log( + `Connecting to MongoDB Atlas (Cloud) at ${process.env.DB_URL} ...` + ); + const conn = await mongoose.connect(process.env.DB_URL); + } + + console.log(` --- MongoDB Connected --- `.cyan); + } catch (error) { + console.error(`Error: ${error.message}`.red.bold); + process.exit(1); + } +}; + +export default connectDB; diff --git a/atm-locator/controllers/atmController.js b/atm-locator/controllers/atmController.js new file mode 100644 index 0000000..b1ed0b5 --- /dev/null +++ b/atm-locator/controllers/atmController.js @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import asyncHandler from "express-async-handler"; +import ATM from "../models/atmModel.js"; + +// @desc Returns list of all ATMs +// @route POST /api/atm +// @access Public +const getATMs = asyncHandler(async (req, res) => { + let query = { + interPlanetary: false, + }; + if (req.body.isOpenNow) { + query.isOpen = true; + } + if (req.body.isInterPlanetary) { + query.interPlanetary = true; + } + const ATMs = await ATM.find(query, { + name: 1, + coordinates: 1, + address: 1, + isOpen: 1, + }); + const shuffledATMs = [...ATMs].sort(() => Math.random() - 0.5).slice(0, 4); + if (shuffledATMs) { + res.status(200).json(shuffledATMs); + } else { + res.status(404).json("No ATMs found"); + throw new Error("No results found"); + } +}); + +// @desc Add new ATM +// @route POST /atm/add +// @access Private +const addATM = asyncHandler(async (req, res) => { + const { + name, + street, + city, + state, + zip, + latitude, + longitude, + monFri, + satSun, + holidays, + atmHours, + numberOfATMs, + isOpen, + interPlanetary, + } = req.body; + const atm = new ATM({ + name, + address: { + street, + city, + state, + zip, + }, + coordinates: { + latitude, + longitude, + }, + timings: { + monFri, + satSun, + holidays, + }, + atmHours, + numberOfATMs, + isOpen, + interPlanetary, + }); + + const createdATM = await atm.save(); + if (createdATM) { + res.status(201).json(createdATM); + } else { + res.status(404); + throw new Error("Could not create ATM"); + } +}); + +// @desc Add specific ATM data +// @route GET /atm/:id +// @access Public +const getSpecificATM = asyncHandler(async (req, res) => { + const atm = await ATM.findById(req.params.id); + if (atm) { + res.status(200).json({ + coordinates: atm.coordinates, + timings: atm.timings, + atmHours: atm.atmHours, + numberOfATMs: atm.numberOfATMs, + isOpen: atm.isOpen, + }); + } else { + res.status(404).json({ message: "ATM information not found" }); + throw new Error("ATM not found"); + } +}); + +export { getATMs, addATM, getSpecificATM }; diff --git a/atm-locator/middleware/errorMiddleware.js b/atm-locator/middleware/errorMiddleware.js new file mode 100644 index 0000000..ef9255d --- /dev/null +++ b/atm-locator/middleware/errorMiddleware.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +const notFound = (req, res, next) => { + const error = new Error(`Not Found - ${req.originalUrl}`); + res.status(404); + next(error); +}; + +const errorHandler = (err, req, res, next) => { + let statusCode = res.statusCode === 200 ? 500 : res.statusCode; + let message = err.message; + + // If Mongoose not found error, set to 404 and change message + if (err.name === "CastError" && err.kind === "ObjectId") { + statusCode = 404; + message = "Resource not found"; + } + + res.status(statusCode).json({ + message: message, + stack: process.env.NODE_ENV === "production" ? null : err.stack, + }); +}; + +export { notFound, errorHandler }; diff --git a/atm-locator/models/atmModel.js b/atm-locator/models/atmModel.js new file mode 100644 index 0000000..6f1b957 --- /dev/null +++ b/atm-locator/models/atmModel.js @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import mongoose from "mongoose"; + +const atmSchema = mongoose.Schema( + { + name: { + type: String, + required: true, + }, + address: { + street: { + type: String, + required: true, + }, + city: { + type: String, + required: true, + }, + state: { + type: String, + required: true, + }, + zip: { + type: String, + required: true, + }, + }, + coordinates: { + latitude: { + type: Number, + required: true, + }, + longitude: { + type: Number, + required: true, + }, + }, + timings: { + monFri: { + type: String, + required: true, + }, + satSun: { + type: String, + required: true, + }, + holidays: { + type: String, + }, + }, + atmHours: { + type: String, + required: true, + }, + numberOfATMs: { + type: Number, + required: true, + }, + isOpen: { + type: Boolean, + required: true, + }, + interPlanetary: { + type: Boolean, + required: true, + default: false, + }, + }, + { + timestamps: true, + } +); + +const ATM = mongoose.model("ATM", atmSchema); + +export default ATM; diff --git a/atm-locator/package-lock.json b/atm-locator/package-lock.json new file mode 100644 index 0000000..881b8a6 --- /dev/null +++ b/atm-locator/package-lock.json @@ -0,0 +1,1386 @@ +{ + "name": "atm-locator", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "atm-locator", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^16.1.4", + "express": "^4.18.2", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.0", + "mongoose": "^7.2.4", + "morgan": "^1.10.0", + "nodemon": "^2.0.22" + } + }, + "node_modules/@types/node": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", + "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", + "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==", + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.1.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", + "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-async-handler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", + "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mongodb": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.5.0.tgz", + "integrity": "sha512-XgrkUgAAdfnZKQfk5AsYL8j7O99WHd4YXPxYxnh8dZxD+ekYWFRA3JktUsBnfg+455Smf75/+asoU/YLwNGoQQ==", + "dependencies": { + "bson": "^5.3.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "saslprep": "^1.0.3" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.201.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongoose": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.2.4.tgz", + "integrity": "sha512-BWcgShV2WH1rspICiJKLPi7QssTebpGJ23Nyk7qG0TMEE/OEAlsQKEhI7VlrXg4ZnoOcHgG+N+upW9tj17TTQg==", + "dependencies": { + "bson": "^5.3.0", + "kareem": "2.5.1", + "mongodb": "5.5.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/atm-locator/package.json b/atm-locator/package.json new file mode 100644 index 0000000..2e15de8 --- /dev/null +++ b/atm-locator/package.json @@ -0,0 +1,26 @@ +{ + "name": "atm-locator", + "version": "1.0.0", + "type": "module", + "description": "atm-locator microservice", + "main": "server.js", + "scripts": { + "start": "node server.js", + "atm": "nodemon server.js" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^16.1.4", + "express": "^4.18.2", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.0", + "mongoose": "^7.2.4", + "morgan": "^1.10.0", + "nodemon": "^2.0.22" + } +} diff --git a/atm-locator/routes/atmRoutes.js b/atm-locator/routes/atmRoutes.js new file mode 100644 index 0000000..8bce5ef --- /dev/null +++ b/atm-locator/routes/atmRoutes.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import express from "express"; +import { + getATMs, + addATM, + getSpecificATM, +} from "../controllers/atmController.js"; + +const router = express.Router(); + +router.post("/", getATMs); +router.post("/add", addATM); +router.get("/:id", getSpecificATM); + +export default router; diff --git a/atm-locator/server.js b/atm-locator/server.js new file mode 100644 index 0000000..6f7ccf0 --- /dev/null +++ b/atm-locator/server.js @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import path from "path"; +import express from "express"; +import dotenv from "dotenv"; + +import cors from "cors"; +import morgan from "morgan"; +import cookieParser from "cookie-parser"; +import colors from "colors"; + +import { notFound, errorHandler } from "./middleware/errorMiddleware.js"; + +import atmRoutes from "./routes/atmRoutes.js"; + +// Load environment variables from .env file +dotenv.config(); + +// connect to MongoDB Atlas database +import connectDB from "./config/db.js"; +connectDB(); + +const port = process.env.PORT || 8001; +const app = express(); + +// mounting middlewares +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use(cors({credentials: true, origin: true})); +app.use(cookieParser()); +app.use(morgan("dev")); + +// mounting routes +app.use("/api/atm", atmRoutes); + +// error handling middlewares +app.use(notFound); +app.use(errorHandler); + +app.listen(port, () => + console.log(`atm-locator server started on port ${port}`.green.bold) +); diff --git a/customer-auth/.dockerignore b/customer-auth/.dockerignore new file mode 100644 index 0000000..6f3479a --- /dev/null +++ b/customer-auth/.dockerignore @@ -0,0 +1,3 @@ +node_modules +Dockerfile +.git \ No newline at end of file diff --git a/customer-auth/.eslintrc.yml b/customer-auth/.eslintrc.yml new file mode 100644 index 0000000..e65e2d7 --- /dev/null +++ b/customer-auth/.eslintrc.yml @@ -0,0 +1,14 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +env: + browser: true + es2021: true +extends: airbnb-base +parserOptions: + ecmaVersion: latest + sourceType: module +rules: + import/extensions: off + no-underscore-dangle: off diff --git a/customer-auth/Dockerfile b/customer-auth/Dockerfile new file mode 100644 index 0000000..160cffd --- /dev/null +++ b/customer-auth/Dockerfile @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 node:14 +WORKDIR /usr/src/app +COPY ./package*.json /usr/src/app +RUN npm install +COPY . ./ +EXPOSE 8000 +CMD ["npm", "start"] \ No newline at end of file diff --git a/customer-auth/config/db.js b/customer-auth/config/db.js new file mode 100644 index 0000000..20df911 --- /dev/null +++ b/customer-auth/config/db.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import mongoose from "mongoose"; + +const connectDB = async () => { + try { + console.log( + ` --- Connecting to MongoDB for customer-auth microservice --- `.cyan + ); + + if (process.env.DATABASE_HOST) { + console.log( + `Connecting to local MongoDB at ${process.env.DATABASE_HOST} ...` + ); + const conn = await mongoose.connect( + `mongodb://${process.env.DATABASE_HOST}:27017/` + ); + } else { + console.log( + `Connecting to MongoDB Atlas for customer-auth at ${process.env.DB_URL}` + ); + const conn = await mongoose.connect(process.env.DB_URL); + } + console.log(` --- MongoDB Connected --- `.cyan); + } catch (error) { + console.error(`Error: ${error.message}`.red.bold); + process.exit(1); + } +}; + +export default connectDB; diff --git a/customer-auth/controllers/userController.js b/customer-auth/controllers/userController.js new file mode 100644 index 0000000..4597e27 --- /dev/null +++ b/customer-auth/controllers/userController.js @@ -0,0 +1,205 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import asyncHandler from "express-async-handler"; +import User from "../models/userModel.js"; +// import { Worker } from 'worker_threads'; +// import generateToken from "../utils/generateToken.js"; + +// @desc Register a new user +// @route POST /api/users +// @access Public +const registerUser = asyncHandler(async (req, res) => { + try { + const { name, email, password } = req.body; + + if (!name || !email || !password) { + res.status(400); + throw new Error("Name, email and password are required"); + } + + const userExists = await User.findOne({ email }); + + if (userExists) { + res.status(400); + throw new Error("User already exists"); + } + + const user = await User.create({ + name, + email, + password, + }); + + if (user) { + // generateToken(res, user._id); + res.status(200).json({ + _id: user._id, + name: user.name, + email: user.email, + }); + } else { + res.status(400); + throw new Error("Invalid user data"); + } + } catch (error) { + res.status(res.statusCode === 200 ? 500 : res.statusCode); + res.json({ + message: error.message || "Internal Server Error", + stack: process.env.NODE_ENV === "production" ? null : error.stack, + }); + } +}); + +// @desc Auth user & get token +// @route POST /api/users/auth +// @access Public +const authUser = asyncHandler(async (req, res) => { + try { + const { email, password } = req.body; + + if (!email || !password) { + res.status(400); + throw new Error("Email and password are required"); + } + + const user = await User.findOne({ email }); + + if (user && (await user.matchPassword(password))) { + // generateToken(res, user._id); + res.json({ + _id: user._id, + name: user.name, + email: user.email, + }); + } else { + res.status(401); + throw new Error("Invalid email or password"); + } + } catch (error) { + res.status(res.statusCode === 200 ? 500 : res.statusCode); + res.json({ + status: false, + message: error.message || "Internal Server Error", + stack: process.env.NODE_ENV === "production" ? null : error.stack, + }); + } + + // const worker = new Worker('./workers/authWorker.js'); + + // worker.on('message', (result) => { + // if (result.valid) { + // generateToken(res, result.user._id); + // res.json({ + // _id: result.user._id, + // name: result.user.name, + // email: result.user.email, + // }); + // } else { + // res.status(401); + // throw new Error('Invalid email or password'); + // } + // }); + // worker.postMessage({ email, password }); +}); + +// @desc Logout user and clear cookie +// @route POST /api/users/logout +// @access Public +const logoutUser = (req, res) => { + try { + // const jwtCookie = req.headers.authorization; + + // if (jwtCookie === undefined) { + // res.status(400); + // throw new Error("No JWT cookie found"); + // } + // else{ + // res.status(200).json({ message: "Logged out successfully" }); + // } + + res.status(200).json({ message: "Logged out successfully" }); + + // res.cookie("jwt", "", { + // httpOnly: true, + // expires: new Date(0), + // }); + // res.status(200).json({ message: "Logged out successfully" }); + } catch (error) { + res.status(res.statusCode === 200 ? 500 : res.statusCode); + res.json({ + message: error.message || "Internal Server Error", + stack: process.env.NODE_ENV === "production" ? null : error.stack, + }); + } +}; + +// @desc Get user profile +// @route GET api/users/profile +// @access Private +const getUserProfile = asyncHandler(async (req, res) => { + try { + const user = await User.findOne(req.email); + + if (user) { + res.json({ + _id: user._id, + name: user.name, + email: user.email, + }); + } else { + res.status(404); + throw new Error("User not found"); + } + } catch (error) { + res.status(res.statusCode === 200 ? 500 : res.statusCode); + res.json({ + message: error.message || "Internal Server Error", + stack: process.env.NODE_ENV === "production" ? null : error.stack, + feedback: "", + }); + } +}); + +// @desc Update user profile +// @route PUT /api/users/profile +// @access Private +const updateUserProfile = asyncHandler(async (req, res) => { + try { + const user = await User.findOne(req.email); + + if (user) { + user.password = req.body.password || user.password; + + const updatedUser = await user.save(); + + res.json({ + _id: updatedUser._id, + name: updatedUser.name, + email: updatedUser.email, + }); + + } else { + res.status(404); + throw new Error("User not found"); + } + } catch (error) { + res.status(res.statusCode === 200 ? 500 : res.statusCode); + res.json({ + message: error.message || "Internal Server Error", + stack: process.env.NODE_ENV === "production" ? null : error.stack, + feedback: "", + }); + } +}); + +export { + authUser, + registerUser, + logoutUser, + getUserProfile, + updateUserProfile, +}; diff --git a/customer-auth/middleware/authMiddleware.js b/customer-auth/middleware/authMiddleware.js new file mode 100644 index 0000000..fbe471d --- /dev/null +++ b/customer-auth/middleware/authMiddleware.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import jwt from "jsonwebtoken"; +import asyncHandler from "express-async-handler"; +import User from "../models/userModel.js"; + +const protect = asyncHandler(async (req, res, next) => { + const token = req.headers.authorization; + + if (token) { + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + + req.user = await User.findById(decoded.userId).select("-password"); + + next(); + } catch (error) { + console.error(error); + res.status(401); + throw new Error("Not authorized, token failed"); + } + } else { + res.status(401); + throw new Error("Not authorized, no token"); + } +}); + +export { protect }; diff --git a/customer-auth/middleware/errorMiddleware.js b/customer-auth/middleware/errorMiddleware.js new file mode 100644 index 0000000..fe1875b --- /dev/null +++ b/customer-auth/middleware/errorMiddleware.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +const notFound = (req, res, next) => { + const error = new Error(`Not Found - ${req.originalUrl}`); + res.status(404); + next(error); +}; + +const errorHandler = (err, req, res) => { + let statusCode = res.statusCode === 200 ? 500 : res.statusCode; + let { message } = err; + + if (err.name === "CastError" && err.kind === "ObjectId") { + statusCode = 404; + message = "Resource not found"; + } + + res.status(statusCode).json({ + message, + stack: process.env.NODE_ENV === "production" ? null : err.stack, + }); +}; + +export { notFound, errorHandler }; diff --git a/customer-auth/models/userModel.js b/customer-auth/models/userModel.js new file mode 100644 index 0000000..b3d0594 --- /dev/null +++ b/customer-auth/models/userModel.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import mongoose from "mongoose"; +import bcrypt from "bcryptjs"; + +const userSchema = mongoose.Schema( + { + name: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + }, + { + timestamps: true, + } +); + +// Match user entered password to hashed password in database +userSchema.methods.matchPassword = async function (enteredPassword) { + const status = await bcrypt.compare(enteredPassword, this.password); + return status; +}; + +// Encrypt password using bcrypt +userSchema.pre("save", async function (next) { + if (!this.isModified("password")) { + next(); + } + + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); +}); + +const User = mongoose.model("User", userSchema); + +export default User; diff --git a/customer-auth/package-lock.json b/customer-auth/package-lock.json new file mode 100644 index 0000000..574c072 --- /dev/null +++ b/customer-auth/package-lock.json @@ -0,0 +1,4567 @@ +{ + "name": "customer-auth", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "customer-auth", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", + "auth": "^0.0.9", + "bcryptjs": "^2.4.3", + "colors": "^1.4.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.0", + "loadtest": "^5.2.0", + "mongoose": "^7.1.0", + "morgan": "^1.10.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^4.6.3" + }, + "devDependencies": { + "concurrently": "^8.0.1", + "eslint": "^8.43.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.27.5", + "nodemon": "^2.0.22" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/node": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", + "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz", + "integrity": "sha512-+MUpcbyxD528dECUBCEVm6abNuORdbuGjbrUdHDeAQ+rkPuo2a+L4N02WJHF3bonSSE6SJ3dUJwF2V6+cHnf0w==" + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz", + "integrity": "sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA==", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz", + "integrity": "sha512-TnB6ziK363p7lR8QpeLC8aMr8EGYBKZTpgzQLfqTs3bR0Oo5VbKdwKf8h0dSzsYrB7lSCgfJnMZKqShvlq5Oyg==", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/auth": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/auth/-/auth-0.0.9.tgz", + "integrity": "sha512-+ThS/vauhMflSBmvFKmjD0y8FzQo8RltMJnI2qvHR0cgSxTg9prCeIv3SDF2e5U5rauSPSBQ7hBAvT4kMVvIKA==", + "dependencies": { + "comerr": "0.0.x", + "dsync": "0.0.x", + "dustjs-linkedin": "1.1.x", + "outcome": "0.0.x", + "seq": "0.3.x", + "step": "0.0.x", + "structr": "0.2.x", + "underscore": "1.4.x", + "verify": "0.0.x", + "vine": "0.1.x" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz", + "integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==", + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chainsaw": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.0.9.tgz", + "integrity": "sha512-nG8PYH+/4xB+8zkV4G844EtfvZ5tTiLFoX3dZ4nhF4t3OCKIb9UvaFyNmeZO2zOSmRWzBoTD+napN6hiL+EgcA==", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chainsaw/node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-table": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.0.2.tgz", + "integrity": "sha512-pxlO2LG8dJMt+xR33zeICgkCje4SSueapy+FW7VjtD9HEUdOj7gl8lNCjCUWa9H4M3ao0ys/1kjLv86KY4tWmA==", + "dependencies": { + "colors": "0.3.0" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-table/node_modules/colors": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.3.0.tgz", + "integrity": "sha512-zRIkNRjxdyFV2Vuq0Bh8hL/rWgQsBM19aB6Uq9CMot2olUuD1DEPon9SB3GZNDrfOojb6a74AQhSM5BKrAr9tA==", + "engines": { + "node": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", + "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/comerr": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/comerr/-/comerr-0.0.9.tgz", + "integrity": "sha512-jVoXtdEY1ntDzwARfeqUoQG1MNNVsIz2jjeM2m6PLkvJoJlhZoo0SAtOCUAvtrPI4wGskrzpkXTOpXk5FyIejQ==" + }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concurrently": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.0.tgz", + "integrity": "sha512-nnLMxO2LU492mTUj9qX/az/lESonSZu81UznYDoXtz1IQf996ixVqPAgHXwvHiHCAef/7S8HIK+fTFK7Ifk8YA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/confinode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/confinode/-/confinode-2.1.1.tgz", + "integrity": "sha512-u5u0ZHpYMnVWtelxjalNtLvL+SdP7B/7s0JTFUIkyvqqIf67DAvy6SKaE6WZiwbufLPk+6zJKsh5SdpbtbFi9g==", + "dependencies": { + "quick-lru": "^5.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cover": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/cover/-/cover-0.2.9.tgz", + "integrity": "sha512-ezch+ax5tw9w+9NDdh65VoMrfM8HfT7l/Tys8G4QQw58Yj7PeJ8DlBKDmV9cZuEUuoBoy4rdjVXKClrh8o6A1g==", + "dependencies": { + "cli-table": "0.0.x", + "underscore": "1.2.x", + "underscore.string": "2.0.x", + "which": "1.0.x" + }, + "bin": { + "cover": "bin/cover" + } + }, + "node_modules/cover/node_modules/underscore": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.2.4.tgz", + "integrity": "sha512-WvFu6gnoSYDtm/hncQgO00QgFTxlmiw2Uv3DoU7yq5aLD4RhX7cUHO4TDU53JWy5E63Ex4yYsm1+9t2cZvefnQ==", + "engines": { + "node": "*" + } + }, + "node_modules/cover/node_modules/which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha512-E87fdQ/eRJr9W1X4wTPejNy9zTW3FI2vpCZSJ/HAY+TkjKVC0TUm1jk6vn2Z7qay0DQy0+RBGdXxj+RmmiGZKQ==", + "bin": { + "which": "bin/which" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "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/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz", + "integrity": "sha512-t2N+4ihO7YgydJOUI47I6GdXpONJ+jUZmYeTNiifALaEduiCja1mKcq3tuSp0RhA9mMfxdMN3YskpwB7puMAtw==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.1.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", + "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dref": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/dref/-/dref-0.0.6.tgz", + "integrity": "sha512-mavsfoF9FiDt11eQccNaBpxRQUNHUOruQX732cYn/L7IJLLo5KXa/FsHwnx4Z2x8hMro9heTo+S/KccdU76J2Q==", + "dependencies": { + "dref": "0.0.x", + "structr": "0.2.x", + "type-component": "0.0.x" + } + }, + "node_modules/dsync": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/dsync/-/dsync-0.0.2.tgz", + "integrity": "sha512-RcvF78M77BRR4Er9Nq8am6gjNGJnJdM1zY7MkIXEnTRC2ZISVrQ3cGEhOqlVbQq+u/UQPG3SAETtIb5fc41X7g==", + "dependencies": { + "deep-extend": "0.2.x", + "dref": "0.0.x", + "traverse": "*", + "underscore": "1.4.x" + } + }, + "node_modules/dustjs-linkedin": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/dustjs-linkedin/-/dustjs-linkedin-1.1.1.tgz", + "integrity": "sha512-nUMxou4m/MgM3oNUiyojj/lRyxq2BHJzvfxVklaeUbi/pXgu83HI/6uOAD1tRlZXJt9VJddkp//y/D1qswnoFA==", + "dependencies": { + "cover": "0.2.x", + "jasmine-node": "1.0.x", + "uglify-js": "1.3.3" + }, + "bin": { + "dustc": "bin/dustc" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "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" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-async-handler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", + "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "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==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hashish": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/hashish/-/hashish-0.0.4.tgz", + "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", + "dependencies": { + "traverse": ">=0.2.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jasmine-node": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.0.28.tgz", + "integrity": "sha512-VKjjoZwIiwJLpKZ46Rozs3TmkGyGUODMwjmdkr62HFtC1Pmv2z5HXsiiPIXms8M2p52uSK+kcQgb3apFNlTiJA==", + "deprecated": "jasmine-node 1.x & 2.x are deprecated, with known vulnerability in jasmine-growl-reporter pre-2.0.0", + "dependencies": { + "coffee-script": ">=1.0.1", + "jasmine-reporters": ">=0.2.0", + "requirejs": ">=0.27.1", + "underscore": ">= 1.3.1", + "walkdir": ">= 0.0.1" + }, + "bin": { + "jasmine-node": "bin/jasmine-node" + } + }, + "node_modules/jasmine-reporters": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.5.2.tgz", + "integrity": "sha512-qdewRUuFOSiWhiyWZX8Yx3YNQ9JG51ntBEO4ekLQRpktxFTwUHy24a86zD/Oi2BRTKksEdfWQZcQFqzjqIkPig==", + "dependencies": { + "@xmldom/xmldom": "^0.8.5", + "mkdirp": "^1.0.4" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/loadtest": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/loadtest/-/loadtest-5.2.0.tgz", + "integrity": "sha512-SwTA3n/IZIqLJHOulycT1fszToe0yck7QUkU8nV4kHSUuamzSmofohjLlwI2vcrZrR1kA3ZC6aDAS04lgCHS0g==", + "dependencies": { + "agentkeepalive": "^2.0.3", + "confinode": "^2.1.1", + "https-proxy-agent": "^2.2.1", + "log": "1.4.*", + "stdio": "^0.2.3", + "testing": "^1.1.1", + "websocket": "^1.0.28" + }, + "bin": { + "loadtest": "bin/loadtest.js", + "testserver-loadtest": "bin/testserver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "node_modules/log": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/log/-/log-1.4.0.tgz", + "integrity": "sha512-NnLhcxIAbhdhuMU0jDG83YjAH8JQj8tXUTy54Ib+4owuXwerrYFI8+OsnK1Ez/cig8O859QK6u6g0aYph/X/zQ==", + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mongodb": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.5.0.tgz", + "integrity": "sha512-XgrkUgAAdfnZKQfk5AsYL8j7O99WHd4YXPxYxnh8dZxD+ekYWFRA3JktUsBnfg+455Smf75/+asoU/YLwNGoQQ==", + "dependencies": { + "bson": "^5.3.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "saslprep": "^1.0.3" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.201.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongoose": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.2.4.tgz", + "integrity": "sha512-BWcgShV2WH1rspICiJKLPi7QssTebpGJ23Nyk7qG0TMEE/OEAlsQKEhI7VlrXg4ZnoOcHgG+N+upW9tj17TTQg==", + "dependencies": { + "bson": "^5.3.0", + "kareem": "2.5.1", + "mongodb": "5.5.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nodemon": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/outcome": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/outcome/-/outcome-0.0.18.tgz", + "integrity": "sha512-r5lBTvoSOO1a4J/bjCy5wOKs0CUEp4+DlL3WnGLgnpWLzNVF7KIUPlYUETbZ81IQHWmvVDZ8c1913uQW65ePNg==" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/seq": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/seq/-/seq-0.3.5.tgz", + "integrity": "sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g==", + "dependencies": { + "chainsaw": ">=0.0.7 <0.1", + "hashish": ">=0.0.2 <0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "dependencies": { + "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/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdio": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/stdio/-/stdio-0.2.7.tgz", + "integrity": "sha512-q7zge680s4BhYx91a8RqHWfiyHM9b+2ZwrNEJJkL6Y6I3F78p9Ag56enyMlpLckCH+bL1shV7YEK1cLXOaA3Bg==", + "engines": { + "node": "*" + } + }, + "node_modules/step": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/step/-/step-0.0.6.tgz", + "integrity": "sha512-qSSeQinUJk2w38vUFobjFoE307GqsozMC8VisOCkJLpklvKPT0ptPHwWOrENoag8rgLudvTkfP3bancwP93/Jw==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/structr": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/structr/-/structr-0.2.4.tgz", + "integrity": "sha512-hL77hWtJhhuhDb4od5ywwAnVUlq9Krzk4BCoWhfiPtTEHvqBqRUm6lJ5PQlCrMWyi8Cv4+AXA/Mu6dbeTN/fGA==" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-jsdoc/node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.1.0.tgz", + "integrity": "sha512-c1KmAjuVODxw+vwkNLALQZrgdlBAuBbr2xSPfYrJgseEi7gFKcTvShysPmyuDI4kcUa1+5rFpjWvXdusKY74mg==" + }, + "node_modules/swagger-ui-express": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.3.tgz", + "integrity": "sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==", + "dependencies": { + "swagger-ui-dist": ">=4.11.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/testing": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/testing/-/testing-1.1.2.tgz", + "integrity": "sha512-+wHrDL29KsI3NQtgGmgdZ/MaUZhnVePbt5ZfiMn6ntDpv/kMWfdiBrg/lJqntor9H8+zQYxvfPLVowPPs1nVEg==", + "dependencies": { + "log": "1.4.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/toarray/-/toarray-0.0.1.tgz", + "integrity": "sha512-4EEt1cngMyDQvStibtjwHav7mCYf0mLAXYQ3z03zNacXjWjIHN01j1AtjGpEuCKX5sea+ZzyZcDXgjitxOVaww==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/traverse": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", + "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-component": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/type-component/-/type-component-0.0.1.tgz", + "integrity": "sha512-mDZRBQS2yZkwRQKfjJvQ8UIYJeBNNWCq+HBNstl9N5s9jZ4dkVYXEGkVPsSCEh5Ld4JM1kmrZTzjnrqSAIQ7dw==" + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/uglify-js": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.3.3.tgz", + "integrity": "sha512-rM1jYODSisv6Ki54DTEec2YvPv11LAdFVLgrY8fqGzwTDrg1tT91Z8nExx0X375TOptjwJh9MSs6KYQez8XCoQ==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha512-ZqGrAgaqqZM7LGRzNjLnw5elevWb5M8LEoDMadxIW3OWbcv72wMMgKdwOKpd5Fqxe8choLD8HN3iSj3TUh/giQ==" + }, + "node_modules/underscore.string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.0.0.tgz", + "integrity": "sha512-36kUytY+40vDx+/MqchfGG6/QDy/FZPR0RY/vKIEU55yAvxivOwE46rIKMQ2uCIfwjxbI0PQpmljQ2MK80oYDA==", + "engines": { + "node": "*" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validator": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verify": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/verify/-/verify-0.0.10.tgz", + "integrity": "sha512-zCsA/Z7S5JaOiWvNlFGBu7cTJIVPQTxS9wLrijoW8JRcxNPkkjINOICGOjpgrndiBcNY5Z24OomihDnZrEMbrA==", + "dependencies": { + "comerr": "0.0.x", + "sift": "0.0.x", + "structr": "0.2.x", + "toarray": "0.0.x", + "validator": "~0.4.13" + } + }, + "node_modules/verify/node_modules/sift": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/sift/-/sift-0.0.18.tgz", + "integrity": "sha512-AGLWQiwxBbdCWGl9g8oOEAnc/2/VmvEL2ZmiQJXg4rLbNA+Wi8beTJV4pemehLAjiqtNNUyLYCFkNmHNm4E6QQ==" + }, + "node_modules/verify/node_modules/validator": { + "version": "0.4.28", + "resolved": "https://registry.npmjs.org/validator/-/validator-0.4.28.tgz", + "integrity": "sha512-BWMI11MpsaRhwwBkNDb3WbeUtms1ZPThZY/wus5inSYZTEAkkeMiII00PHqzMeBC1po6tpkQ+Z22Dgbv9GHqbA==", + "engines": { + "node": ">=0.2.2" + } + }, + "node_modules/vine": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/vine/-/vine-0.1.1.tgz", + "integrity": "sha512-NBiN0phDxRdNHdBmmnzSDrO5xj1G+VGRJjoDaq7CeaF6x5Qx0a78ZHVM/A36xSAznoFRWCybVjFMq5wDYlDOtw==", + "dependencies": { + "outcome": "*" + } + }, + "node_modules/walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + } + } +} diff --git a/customer-auth/package.json b/customer-auth/package.json new file mode 100644 index 0000000..d28cf9d --- /dev/null +++ b/customer-auth/package.json @@ -0,0 +1,39 @@ +{ + "name": "customer-auth", + "version": "1.0.0", + "type": "module", + "description": "customer-auth microservice", + "main": "server.js", + "scripts": { + "start": "node server.js", + "auth": "nodemon server.js", + "lint": "npx eslint --fix ." + }, + "author": "Jash Mehta", + "license": "MIT", + "dependencies": { + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", + "auth": "^0.0.9", + "bcryptjs": "^2.4.3", + "colors": "^1.4.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.0", + "loadtest": "^5.2.0", + "mongoose": "^7.1.0", + "morgan": "^1.10.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^4.6.3" + }, + "devDependencies": { + "concurrently": "^8.0.1", + "eslint": "^8.43.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.27.5", + "nodemon": "^2.0.22" + } +} diff --git a/customer-auth/routes/userRoutes.js b/customer-auth/routes/userRoutes.js new file mode 100644 index 0000000..203c55b --- /dev/null +++ b/customer-auth/routes/userRoutes.js @@ -0,0 +1,359 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import express from "express"; +import { + authUser, + registerUser, + logoutUser, + getUserProfile, + updateUserProfile, +} from "../controllers/userController.js"; +import { protect } from "../middleware/authMiddleware.js"; + +const router = express.Router(); + +/** + * @openapi + * /api/users/: + * post: + * summary: Register a new user + * description: Register a new user with the provided name, email, and password. + * tags: + * - Authentication + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * email: + * type: string + * password: + * type: string + * required: + * - name + * - email + * - password + * responses: + * '200': + * description: User successfully registered + * content: + * application/json: + * schema: + * type: object + * properties: + * _id: + * type: string + * description: The ID of the registered user + * name: + * type: string + * description: The name of the registered user + * email: + * type: string + * description: The email of the registered user + * headers: + * Set-Cookie: + * description: Authentication cookie containing the access token + * schema: + * type: string + * '400': + * description: Invalid user data or user already exists + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message describing the issue + * '500': + * description: Internal Server Error + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message or "Internal Server Error" + * stack: + * type: string + * description: Stack trace (only in development mode) + */ + +router.post("/", registerUser); + +/** + * @swagger + * /api/users/auth: + * post: + * summary: Authenticate user and get token + * description: Authenticate a user with the provided email and password and retrieve an access token. + * tags: + * - Authentication + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * password: + * type: string + * required: + * - email + * - password + * responses: + * '200': + * description: User authenticated successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * _id: + * type: string + * description: The ID of the authenticated user + * name: + * type: string + * description: The name of the authenticated user + * email: + * type: string + * description: The email of the authenticated user + * headers: + * Set-Cookie: + * description: Authentication cookie containing the access token + * schema: + * type: string + * '400': + * description: Invalid email or password + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message describing the issue + * '500': + * description: Internal Server Error + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message or "Internal Server Error" + * stack: + * type: string + * description: Stack trace (only in development mode) + */ +router.post("/auth", authUser); + +/** + * @swagger + * /api/users/logout: + * post: + * summary: Logout user and clear authentication + * description: Logout the currently authenticated user and clear the JWT token from the request headers. + * tags: + * - Authentication + * parameters: + * - in: header + * name: Authorization + * description: JWT token in the format "\" + * required: true + * schema: + * type: string + * responses: + * '200': + * description: User logged out successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Success message indicating successful logout + * '400': + * description: No JWT token found in the request headers + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message indicating the absence of a JWT token + * '500': + * description: Internal Server Error + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message or "Internal Server Error" + * stack: + * type: string + * description: Stack trace (only in development mode) + */ +router.post("/logout", logoutUser); + +/** + * @swagger + * /api/users/profile: + * get: + * summary: Get user profile + * description: Get the profile of the currently authenticated user. + * tags: + * - User Profile + * security: + * - cookieAuth: [] + * responses: + * '200': + * description: User profile retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * _id: + * type: string + * description: The ID of the user + * name: + * type: string + * description: The name of the user + * email: + * type: string + * description: The email of the user + * createdAt: + * type: string + * format: date-time + * description: The creation date of the user profile + * updatedAt: + * type: string + * format: date-time + * description: The last update date of the user profile + * '401': + * description: User is not authenticated or the token is invalid + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message indicating authentication failure + * '500': + * description: Internal Server Error + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message or "Internal Server Error" + * stack: + * type: string + * description: Stack trace (only in development mode) + * put: + * summary: Update user profile + * description: Update the profile of the currently authenticated user. + * tags: + * - User Profile + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: The new name of the user + * required: + * - name + * responses: + * '200': + * description: User profile updated successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * _id: + * type: string + * description: The ID of the updated user profile + * name: + * type: string + * description: The updated name of the user + * email: + * type: string + * description: The email of the user + * createdAt: + * type: string + * format: date-time + * description: The creation date of the user profile + * updatedAt: + * type: string + * format: date-time + * description: The last update date of the user profile + * '400': + * description: Invalid request data or missing required fields + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message describing the issue + * '401': + * description: User is not authenticated or the token is invalid + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message indicating authentication failure + * '500': + * description: Internal Server Error + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message or "Internal Server Error" + * stack: + * type: string + * description: Stack trace (only in development mode) + */ + +router.route("/profile").get(getUserProfile).put(updateUserProfile); + +// router +// .route("/profile") +// .get(protect, getUserProfile) +// .put(protect, updateUserProfile); + +export default router; diff --git a/customer-auth/server.js b/customer-auth/server.js new file mode 100644 index 0000000..3347f8e --- /dev/null +++ b/customer-auth/server.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import path from 'path'; +import express from 'express'; +import dotenv from 'dotenv'; + +import cors from 'cors'; +import morgan from 'morgan'; +import cookieParser from 'cookie-parser'; +import colors from 'colors'; + +import { swaggerDocs } from './utils/swagger.js'; + +import { notFound, errorHandler } from './middleware/errorMiddleware.js'; + +import userRoutes from './routes/userRoutes.js'; + +// Load environment variables from .env file +dotenv.config(); + +// connect to MongoDB Atlas database +import connectDB from './config/db.js'; +connectDB(); + +const port = process.env.PORT || 8000; +const app = express(); + +// mounting middlewares +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use(cors({credentials: true, origin: true})); +app.use(cookieParser()); +app.use(morgan('dev')); + +// mounting routes +app.use('/api/users', userRoutes); + +// Swagger documentation +swaggerDocs(app, port); + +// error handling middlewares +app.use(notFound); +app.use(errorHandler); + +app.listen(port, () => { + console.log(`customer-api server started on port ${port}`.green.bold); +}); diff --git a/customer-auth/utils/generateToken.js b/customer-auth/utils/generateToken.js new file mode 100644 index 0000000..7025bf3 --- /dev/null +++ b/customer-auth/utils/generateToken.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import jwt from 'jsonwebtoken'; + +// Generate and set a JWT token as a cookie in the response +const generateToken = (res, userId) => { + const token = jwt.sign({ userId }, process.env.JWT_SECRET, { + expiresIn: '30d', + }); + + res.cookie('jwt', token, { + // httpOnly: true, + // secure: true, // Use secure cookies in production + // sameSite: 'strict', // Prevent CSRF attacks + maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days + }); +}; + +export default generateToken; diff --git a/customer-auth/utils/swagger.js b/customer-auth/utils/swagger.js new file mode 100644 index 0000000..2a5847f --- /dev/null +++ b/customer-auth/utils/swagger.js @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import swaggerJSDoc from "swagger-jsdoc"; +import swaggerUI from "swagger-ui-express"; + +// Swagger options +const swaggerOptions = { + definition: { + openapi: "3.0.0", + info: { + title: "customer-auth", + version: "1.0.0", + description: "API documentation for the customer-auth microservice", + }, + }, + apis: ["./routes/userRoutes.js"], +}; + +// Create Swagger specification object +const swaggerSpec = swaggerJSDoc(swaggerOptions); + +export const swaggerDocs = (app, port) => { + // Swagger page + app.use( + "/docs", + swaggerUI.serve, + swaggerUI.setup(swaggerSpec, { explorer: true }) + ); + + // Documentation in JSON format + app.get("/docs.json", (req, res) => { + res.setHeader("Content-Type", "application/json"); + res.send(swaggerSpec); + }); + + console.log( + `Swagger documentation available at :${port}/docs` + ); +}; diff --git a/customer-auth/workers/authWorker.js b/customer-auth/workers/authWorker.js new file mode 100644 index 0000000..3b6e981 --- /dev/null +++ b/customer-auth/workers/authWorker.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { parentPort } from "worker_threads"; +import User from "../models/userModel.js"; +import connectDB from "../config/db.js"; + +parentPort.on("message", async (message) => { + const { email, password } = message; + + try { + connectDB(); + const user = await User.findOne({ email }); + + if (user && (await user.matchPassword(password))) { + parentPort.postMessage({ + valid: true, + user: { + _id: user.id, + name: user.name, + email: user.email, + }, + }); + } else { + parentPort.postMessage({ valid: false }); + } + } catch (error) { + parentPort.postMessage({ valid: false, error: error.message }); + } +}); diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile new file mode 100644 index 0000000..6b77d73 --- /dev/null +++ b/dashboard/Dockerfile @@ -0,0 +1,22 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 python + +RUN mkdir /service +COPY protobufs/ /service/protobufs/ +COPY dashboard/ /service/dashboard/ +WORKDIR /service/dashboard +RUN python -m pip install --upgrade pip +RUN python -m pip install -r requirements.txt + + +RUN python -m grpc_tools.protoc -I ../protobufs --python_out=. --grpc_python_out=. ../protobufs/accounts.proto +RUN python -m grpc_tools.protoc -I ../protobufs --python_out=. --grpc_python_out=. ../protobufs/loan.proto +RUN python -m grpc_tools.protoc -I ../protobufs --python_out=. --grpc_python_out=. ../protobufs/transaction.proto + + +EXPOSE 5000 +ENV FLASK_APP=dashboard.py +ENTRYPOINT [ "flask", "run", "--host=0.0.0.0"] \ No newline at end of file diff --git a/dashboard/accounts_pb2.py b/dashboard/accounts_pb2.py new file mode 100644 index 0000000..ac6ba3e --- /dev/null +++ b/dashboard/accounts_pb2.py @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: accounts.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x61\x63\x63ounts.proto\"\xbf\x01\n\x07\x41\x63\x63ount\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\x12\x10\n\x08\x65mail_id\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x03 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x04 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x05 \x01(\t\x12\x1a\n\x12government_id_type\x18\x06 \x01(\t\x12\x0c\n\x04name\x18\x07 \x01(\t\x12\x10\n\x08\x63urrency\x18\x08 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\t \x01(\x01\"\x91\x01\n\x14\x43reateAccountRequest\x12\x10\n\x08\x65mail_id\x18\x01 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x02 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x04 \x01(\t\x12\x1a\n\x12government_id_type\x18\x05 \x01(\t\x12\x0c\n\x04name\x18\x06 \x01(\t\"\'\n\x15\x43reateAccountResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"&\n\x12GetAccountsRequest\x12\x10\n\x08\x65mail_id\x18\x01 \x01(\t\"1\n\x13GetAccountsResponse\x12\x1a\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x08.Account\"X\n\rAccountDetail\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07\x62\x61lance\x18\x03 \x01(\x01\x12\x10\n\x08\x63urrency\x18\x04 \x01(\t\"1\n\x17GetAccountDetailRequest\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t2\xd0\x01\n\x15\x41\x63\x63ountDetailsService\x12=\n\x11getAccountDetails\x12\x18.GetAccountDetailRequest\x1a\x0e.AccountDetail\x12>\n\rcreateAccount\x12\x15.CreateAccountRequest\x1a\x16.CreateAccountResponse\x12\x38\n\x0bgetAccounts\x12\x13.GetAccountsRequest\x1a\x14.GetAccountsResponseb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'accounts_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _ACCOUNT._serialized_start=19 + _ACCOUNT._serialized_end=210 + _CREATEACCOUNTREQUEST._serialized_start=213 + _CREATEACCOUNTREQUEST._serialized_end=358 + _CREATEACCOUNTRESPONSE._serialized_start=360 + _CREATEACCOUNTRESPONSE._serialized_end=399 + _GETACCOUNTSREQUEST._serialized_start=401 + _GETACCOUNTSREQUEST._serialized_end=439 + _GETACCOUNTSRESPONSE._serialized_start=441 + _GETACCOUNTSRESPONSE._serialized_end=490 + _ACCOUNTDETAIL._serialized_start=492 + _ACCOUNTDETAIL._serialized_end=580 + _GETACCOUNTDETAILREQUEST._serialized_start=582 + _GETACCOUNTDETAILREQUEST._serialized_end=631 + _ACCOUNTDETAILSSERVICE._serialized_start=634 + _ACCOUNTDETAILSSERVICE._serialized_end=842 +# @@protoc_insertion_point(module_scope) diff --git a/dashboard/accounts_pb2_grpc.py b/dashboard/accounts_pb2_grpc.py new file mode 100644 index 0000000..6acacb6 --- /dev/null +++ b/dashboard/accounts_pb2_grpc.py @@ -0,0 +1,148 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import accounts_pb2 as accounts__pb2 + + +class AccountDetailsServiceStub(object): + """message GetAccountDetailResponse { + AccountDetail account = 1; + } + + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.getAccountDetails = channel.unary_unary( + '/AccountDetailsService/getAccountDetails', + request_serializer=accounts__pb2.GetAccountDetailRequest.SerializeToString, + response_deserializer=accounts__pb2.AccountDetail.FromString, + ) + self.createAccount = channel.unary_unary( + '/AccountDetailsService/createAccount', + request_serializer=accounts__pb2.CreateAccountRequest.SerializeToString, + response_deserializer=accounts__pb2.CreateAccountResponse.FromString, + ) + self.getAccounts = channel.unary_unary( + '/AccountDetailsService/getAccounts', + request_serializer=accounts__pb2.GetAccountsRequest.SerializeToString, + response_deserializer=accounts__pb2.GetAccountsResponse.FromString, + ) + + +class AccountDetailsServiceServicer(object): + """message GetAccountDetailResponse { + AccountDetail account = 1; + } + + """ + + def getAccountDetails(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def createAccount(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getAccounts(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AccountDetailsServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'getAccountDetails': grpc.unary_unary_rpc_method_handler( + servicer.getAccountDetails, + request_deserializer=accounts__pb2.GetAccountDetailRequest.FromString, + response_serializer=accounts__pb2.AccountDetail.SerializeToString, + ), + 'createAccount': grpc.unary_unary_rpc_method_handler( + servicer.createAccount, + request_deserializer=accounts__pb2.CreateAccountRequest.FromString, + response_serializer=accounts__pb2.CreateAccountResponse.SerializeToString, + ), + 'getAccounts': grpc.unary_unary_rpc_method_handler( + servicer.getAccounts, + request_deserializer=accounts__pb2.GetAccountsRequest.FromString, + response_serializer=accounts__pb2.GetAccountsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'AccountDetailsService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class AccountDetailsService(object): + """message GetAccountDetailResponse { + AccountDetail account = 1; + } + + """ + + @staticmethod + def getAccountDetails(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/AccountDetailsService/getAccountDetails', + accounts__pb2.GetAccountDetailRequest.SerializeToString, + accounts__pb2.AccountDetail.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def createAccount(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/AccountDetailsService/createAccount', + accounts__pb2.CreateAccountRequest.SerializeToString, + accounts__pb2.CreateAccountResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getAccounts(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/AccountDetailsService/getAccounts', + accounts__pb2.GetAccountsRequest.SerializeToString, + accounts__pb2.GetAccountsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/dashboard/dashboard.py b/dashboard/dashboard.py new file mode 100644 index 0000000..fadfe27 --- /dev/null +++ b/dashboard/dashboard.py @@ -0,0 +1,723 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +import os +import logging +import json + +# from google.protobuf.json_format import MessageToDict +from flask_cors import CORS + +from flask import Flask, render_template, request, jsonify +import grpc + +from dotenv import load_dotenv +load_dotenv() + +from accounts_pb2 import * +from accounts_pb2_grpc import * + +from transaction_pb2_grpc import * +from transaction_pb2 import * + +from loan_pb2_grpc import LoanServiceStub +from loan_pb2 import * + +from pymongo.mongo_client import MongoClient + +import requests as flask_client_requests + +# set logging to debug +logging.basicConfig(level=logging.DEBUG) + + +# db_host = os.getenv("DATABASE_HOST", "localhost") +db_url = os.getenv("DB_URL") +if db_url is None: + raise Exception("DB_URL environment variable is not set") + +uri = db_url + +logging.debug(f"Connecting to MongoDB at {uri}") + +# protocol = os.getenv('SERVICE_PROTOCOL') +protocol = os.getenv('SERVICE_PROTOCOL', 'http') + +if protocol is None: + raise Exception("SERVICE_PROTOCOL environment variable is not set") + +protocol = protocol.lower() +logging.debug(f"microservice protocol: {protocol}") + + +client = MongoClient(uri) +db = client["bank"] +collection = db["accounts"] + + +app = Flask(__name__) +CORS(app) + + +@app.route("/") +def render_homepage(): + return f"Dashboard is running..." + + +# @app.route("/") +# def render_homepage(): +# account_details_host = os.getenv("ACCOUNT_DETAILS_HOST", "localhost") +# account_details_channel = grpc.insecure_channel(f"{account_details_host}:50051") +# account_details_client = AccountDetailsServiceStub(account_details_channel) + +# account_details_request = GetAccountDetailsRequest(account_number="1") +# account_details_response = account_details_client.GetAccountDetails( +# account_details_request +# ) +# return render_template( +# "homepage.html", account=account_details_response.account +# ) + +# gRPC setup + + +@app.route("/account/create", methods=["GET", "POST"]) +def create_account(): + def __grpc(): + channel = grpc.insecure_channel(host_ip_port) + client = AccountDetailsServiceStub(channel) + email_id = request.form["email_id"] + account_type = request.form["account_type"] + address = request.form["address"] + govt_id_number = request.form["govt_id_number"] + government_id_type = request.form["government_id_type"] + name = request.form["name"] + + # Create a gRPC request + account_request = CreateAccountRequest( + email_id=email_id, + account_type=account_type, + address=address, + govt_id_number=govt_id_number, + government_id_type=government_id_type, + name=name, + ) + + logging.debug(f"Sending account creation request: {account_request}") + # Send the gRPC request to the Account Microservice + response = client.createAccount(account_request) + logging.debug(f"Account creation response: {response}") + + logging.debug(f"Account creation response: {response.result}") + + # Return a JSON response + return json.dumps({"response": {"status": response.result}}) + + def __flask(): + response = flask_client_requests.post( + f"http://{host_ip_port}/create-account", json=request.form + ) + logging.debug(f"====================== {response.json()}") + return {"response": response.json()} + + accounts_host = os.getenv("ACCOUNT_HOST", "localhost") + host_ip_port = f"{accounts_host}:50051" + + if request.method == "POST": + logging.debug("+++++++++++++++++++++++++++++++++++++++++") + logging.debug(request.form) + # result = __grpc() + # result = __flask() + result = None + if protocol == "grpc": + result = __grpc() + else: + result = __flask() + + + return result + + return render_template("create_account_form.html") + + +# a_b + + +@app.route("/account/allaccounts", methods=["GET", "POST"]) +def get_all_accounts(): + def __grpc(): + channel = grpc.insecure_channel(host_ip_port) + client = AccountDetailsServiceStub(channel) + logging.debug("+++++++++++++++++++++++++++++++++++++++++") + logging.debug(request.form) + + email_id = request.form["email_id"] + get_req = GetAccountsRequest(email_id=email_id) + response = client.getAccounts(get_req) + response = [ + { + "account_number": acc.account_number, + "email_id": acc.email_id, + "account_type": acc.account_type, + "address": acc.address, + "govt_id_number": acc.govt_id_number, + "government_id_type": acc.government_id_type, + "name": acc.name, + "currency": acc.currency, + "balance": acc.balance, + } + for acc in response.accounts + ] + + return json.dumps({"response": response}) + + def __flask(): + response = flask_client_requests.post( + f"http://{host_ip_port}/get-all-accounts", json=request.form + ) + logging.debug(f"====================== {response.json()}") + return {"response": response.json()} + + accounts_host = os.getenv("ACCOUNT_HOST", "localhost") + host_ip_port = f"{accounts_host}:50051" + if request.method == "POST": + # response = __grpc() + # response = __flask() + response = None + if protocol == "grpc": + response = __grpc() + else: + response = __flask() + + return response + + return jsonify({"response": None}) + + +@app.route("/account/detail", methods=["GET", "POST"]) +def get_account_details(): + def __grpc(): + logging.debug(" get account details called") + channel = grpc.insecure_channel(host_ip_port) + client = AccountDetailsServiceStub(channel) + + account_number = request.form["account_number"] + get_req = GetAccountDetailRequest(account_number=account_number) + response = client.getAccountDetails(get_req) + + return json.dumps( + { + "response": { + "account_number": response.account_number, + "name": response.name, + "balance": response.balance, + "currency": response.currency, + } + } + ) + + def __flask(): + response = flask_client_requests.post( + f"http://{host_ip_port}/account-detail", json=request.form + ) + logging.debug(f"====================== {response.json()}") + return {"response": response.json()} + + accounts_host = os.getenv("ACCOUNT_HOST", "localhost") + host_ip_port = f"{accounts_host}:50051" + + if request.method == "POST": + logging.debug("+++++++++++++++++++++++++++++++++++++++++") + logging.debug(request.form) + # response = __grpc() + # response = __flask() + response = None + if protocol == "grpc": + response = __grpc() + else: + response = __flask() + + return response + + return jsonify({"response": None}) + + +@app.route("/transaction/", methods=["GET", "POST"]) +def transaction_form(): + def __grpc(): + channel = grpc.insecure_channel(host_ip_port) + client = TransactionServiceStub(channel) + sender_account_number = request.form["sender_account_number"] # type: ignore + receiver_account_number = request.form["receiver_account_number"] # type: ignore + amount = float(request.form["amount"]) # type: ignore + sender_account_type = request.form["sender_account_type"] # type: ignore + receiver_account_type = request.form["receiver_account_type"] # type: ignore + reason = request.form["reason"] # type: ignore + req = TransactionRequest( + sender_account_number=sender_account_number, + receiver_account_number=receiver_account_number, + amount=amount, + sender_account_type=sender_account_type, + receiver_account_type=receiver_account_type, + reason=reason, + ) + + logging.debug("Sending transaction request...") + + response = client.sendMoney(req) + # return f"Transaction successful. Transaction ID: {response}" + return json.dumps( + {"response": {"approved": response.approved, "message": response.message}} + ) + + def __flask(): + response = flask_client_requests.post( + f"http://{host_ip_port}/transfer", json=request.form + ) + logging.debug(f"====================== {response.json()}") + return {"response": response.json()} + + transaction_host = os.getenv("TRANSACTION_HOST", "localhost") + host_ip_port = f"{transaction_host}:50052" + if request.method == "POST": + # result = __grpc() + # result = __flask() + result = None + if protocol == "grpc": + result = __grpc() + else: + result = __flask() + + logging.debug(f"---->Transaction response: {result}") + return result + + return render_template("transaction.html") + + +@app.route("/transaction/zelle/", methods=["GET", "POST"]) +def transaction_zelle(): + def __grpc(): + channel = grpc.insecure_channel(host_ip_port) + client = TransactionServiceStub(channel) + sender_email = request.form["sender_email"] # type: ignore + receiver_email = request.form["receiver_email"] # type: ignore + amount = float(request.form["amount"]) # type: ignore + reason = request.form["reason"] # type: ignore + + req = ZelleRequest( + sender_email=sender_email, + receiver_email=receiver_email, + amount=amount, + reason=reason, + ) + + logging.debug("Sending transaction request...") + + response = client.Zelle(req) + + logging.debug(f"Zelle response: {response}") + + # return f"Transaction successful. Transaction ID: {response}" + return json.dumps( + {"response": {"approved": response.approved, "message": response.message}} + ) + + def __flask(): + req = { + "sender_email": request.form["sender_email"], + "receiver_email": request.form["receiver_email"], + "amount": float(request.form["amount"]), + "reason": request.form["reason"], + } + response = flask_client_requests.post(f"http://{host_ip_port}/zelle", json=req) + logging.debug(f"====================== {response.json()}") + return {"response": response.json()} + + transaction_host = os.getenv("TRANSACTION_HOST", "localhost") + host_ip_port = f"{transaction_host}:50052" + if request.method == "POST": + # result = __grpc() + # result = __flask() + + result = None + if protocol == "grpc": + result = __grpc() + else: + result = __flask() + + logging.debug(f"---->Transaction response: {result}") + return result + + return render_template("transaction.html") + + +@app.route("/transaction/history", methods=["GET", "POST"]) +def get_all_transactions(): + def __grpc(): + channel = grpc.insecure_channel(host_ip_port) + client = TransactionServiceStub(channel) + + account_number = request.form["account_number"] # type: ignore + req = GetALLTransactionsRequest(account_number=account_number) + response = client.getTransactionsHistory(req) + transaction_history = [] + for r in response.transactions: + t = { + "account_number": r.account_number, + "amount": r.amount, + "reason": r.reason, + "time_stamp": r.time_stamp, + "type": r.type, + "transaction_id": r.transaction_id, + } + transaction_history.append(t) + return json.dumps({"response": transaction_history}) + + def __flask(): + req = {"account_number": request.form["account_number"]} + response = flask_client_requests.post( + f"http://{host_ip_port}/transaction-history", json=req + ) + logging.debug(f"====================== {response.json()}") + return {"response": response.json()} + + transaction_host = os.getenv("TRANSACTION_HOST", "localhost") + host_ip_port = f"{transaction_host}:50052" + if request.method == "POST": + # result = __grpc() + # result = __flask() + result = None + if protocol == "grpc": + result = __grpc() + else: + result = __flask() + logging.debug(f"---->Transaction response: {result}") + return result + + return json.dumps({"response": None}) + + +@app.route("/transaction/transaction-with-id", methods=["GET", "POST"]) +def GetTransactionByID(): + def __grpc(): + transaction_id = request.form["transaction_id"] # type: ignore + channel = grpc.insecure_channel(host_ip_port) + client = TransactionServiceStub(channel) + req = TransactionByIDRequest(transaction_id=transaction_id) + r = client.getTransactionByID(req) + response = { + "account_number": r.account_number, + "amount": r.amount, + "reason": r.reason, + "time_stamp": r.time_stamp, + "type": r.type, + "transaction_id": r.transaction_id, + } + return json.dumps({"response": response}) + + def __flask(): + req = {"transaction_id": request.form["transaction_id"]} + response = flask_client_requests.post( + f"http://{host_ip_port}/transaction-with-id", json=req + ) + logging.debug(f"====================== {response.json()}") + return {"response": response.json()} + + transaction_host = os.getenv("TRANSACTION_HOST", "localhost") + host_ip_port = f"{transaction_host}:50052" + if request.method == "POST": + # result = __grpc() + # result = __flask() + + result = None + if protocol == "grpc": + result = __grpc() + else: + result = __flask() + + logging.debug(f"---->Transaction response: {result}") + return result + + return json.dumps({"response": None}) + + +@app.route("/loan/", methods=["GET", "POST"]) +def loan_form(): + def __getLoanGRPC(): + name = request.form["name"] + email = request.form["email"] + account_type = request.form["account_type"] + account_number = request.form["account_number"] + govt_id_type = request.form["govt_id_type"] + govt_id_number = request.form["govt_id_number"] + loan_type = request.form["loan_type"] + loan_amount = float(request.form["loan_amount"]) + interest_rate = float(request.form["interest_rate"]) + time_period = request.form["time_period"] + + # Create a gRPC request + loan_request = LoanRequest( + name=name, + email=email, + account_type=account_type, + account_number=account_number, + govt_id_type=govt_id_type, + govt_id_number=govt_id_number, + loan_type=loan_type, + loan_amount=loan_amount, + interest_rate=interest_rate, + time_period=time_period, + ) + + # Send the gRPC request to the Loan Microservice + channel = grpc.insecure_channel(host_ip_port) + client = LoanServiceStub(channel) + response = client.ProcessLoanRequest(loan_request) + # response.account_number = account_number + logging.debug(f"Loan response: {response.approved}") + return {"approved": response.approved, "message": response.message} + + def __getLoanFlask(): + # send a request to loan microservice implemented in flask + name = request.form["name"] + email = request.form["email"] + account_type = request.form["account_type"] + account_number = request.form["account_number"] + govt_id_type = request.form["govt_id_type"] + govt_id_number = request.form["govt_id_number"] + loan_type = request.form["loan_type"] + loan_amount = float(request.form["loan_amount"]) + interest_rate = float(request.form["interest_rate"]) + time_period = request.form["time_period"] + + loan_request = { + "name": name, + "email": email, + "account_type": account_type, + "account_number": account_number, + "govt_id_type": govt_id_type, + "govt_id_number": govt_id_number, + "loan_type": loan_type, + "loan_amount": loan_amount, + "interest_rate": interest_rate, + "time_period": time_period, + } + + logging.debug(f"........==============> {loan_request}") + + logging.debug(f"........==============> http://{host_ip_port}/loan/request") + response = flask_client_requests.post( + f"http://{host_ip_port}/loan/request", json=loan_request + ) + return response.json() + + loan_host = os.getenv("LOAN_HOST", "localhost") + host_ip_port = f"{loan_host}:50053" + if request.method == "POST": + # result = __getLoanGRPC() + # result = __getLoanFlask() + + result = None + if protocol == "grpc": + result = __getLoanGRPC() + else: + result = __getLoanFlask() + + logging.debug(f"---->Loan response: {result}") + return json.dumps({"response": result}) + + return render_template("loan_form.html") + + +@app.route("/loan/history", methods=["GET", "POST"]) +def loan_history(): + def __grpc(): + # Send the gRPC request to the Loan Microservice + channel = grpc.insecure_channel(host_ip_port) + client = LoanServiceStub(channel) + req = LoansHistoryRequest(email=request.form["email"]) + response = client.getLoanHistory(req) + loans = [] + for r in response.loans: + t = { + "name": r.name, + "email": r.email, + "account_type": r.account_type, + "account_number": r.account_number, + "govt_id_type": r.govt_id_type, + "govt_id_number": r.govt_id_number, + "loan_type": r.loan_type, + "loan_amount": r.loan_amount, + "interest_rate": r.interest_rate, + "time_period": r.time_period, + "status": r.status, + "timestamp": r.timestamp, + } + loans.append(t) + return loans + + # return MessageToDict(response) + + def __flask(): + # send a post request to loan microservice implemented in flask + req = {"email": request.form["email"]} + logging.debug( + f'=========================> this is {f"{host_ip_port}/loan/history"}' + ) + response = flask_client_requests.post( + f"http://{host_ip_port}/loan/history", json=req + ) + logging.debug(f"====================== {response.json()}") + return response.json() + + loan_host = os.getenv("LOAN_HOST", "localhost") + host_ip_port = f"{loan_host}:50053" + if request.method == "POST": + logging.debug("+++++++++++++++++++++++++++++++++++++++++") + + # response = __grpc() + # response = __flask() + + response = None + if protocol == "grpc": + response = __grpc() + else: + response = __flask() + + + + logging.debug("-----------------------------------------") + + return json.dumps({"response": response}) + return json.dumps({"response": None}) + + +#################### Proxy Routes for API Clarity #################### + + +@app.route("/api/users", methods=["POST"]) +def register_user(): + logging.debug("=========================> register user called") + + customer_auth_host = os.getenv("CUSTOMER_AUTH_HOST", "localhost") + logging.debug( + f"=========================> forwarding to {customer_auth_host}:8000/api/users" + ) + + user_data = flask_client_requests.post( + f"http://{customer_auth_host}:8000/api/users", json=request.json + ).json() + logging.debug( + f"=========================> response from {customer_auth_host}:8000/api/users: {user_data}" + ) + + return json.dumps(user_data) + + +@app.route("/api/users/auth", methods=["POST"]) +def login_user(): + logging.debug("=========================> login user called") + + customer_auth_host = os.getenv("CUSTOMER_AUTH_HOST", "localhost") + logging.debug( + f"=========================> forwarding to {customer_auth_host}:8000/api/users/auth" + ) + + user_data = flask_client_requests.post( + f"http://{customer_auth_host}:8000/api/users/auth", json=request.json + ).json() + logging.debug( + f"=========================> response from {customer_auth_host}:8000/api/users/auth: {user_data}" + ) + + return json.dumps(user_data) + + +@app.route("/api/users/logout", methods=["POST"]) +def logout_user(): + logging.debug("=========================> logout user called") + + customer_auth_host = os.getenv("CUSTOMER_AUTH_HOST", "localhost") + logging.debug( + f"=========================> forwarding to {customer_auth_host}:8000/api/users/logout" + ) + + user_data = flask_client_requests.post( + f"http://{customer_auth_host}:8000/api/users/logout", json=request.json + ).json() + logging.debug( + f"=========================> response from {customer_auth_host}:8000/api/users/logout: {user_data}" + ) + + return json.dumps(user_data) + + +@app.route("/api/users/profile", methods=["GET", "PUT"]) +def profile_user(): + logging.debug("=========================> profile user called") + + customer_auth_host = os.getenv("CUSTOMER_AUTH_HOST", "localhost") + logging.debug( + f"=========================> forwarding to {customer_auth_host}:8000/api/users/profile" + ) + + if request.method == "GET": + user_data = flask_client_requests.get( + f"http://{customer_auth_host}:8000/api/users/profile", json=request.json + ).json() + logging.debug( + f"=========================> response from {customer_auth_host}:8000/api/users/profile: {user_data}" + ) + + if request.method == "PUT": + user_data = flask_client_requests.put( + f"http://{customer_auth_host}:8000/api/users/profile", json=request.json + ).json() + logging.debug( + f"=========================> response from {customer_auth_host}:8000/api/users/profile: {user_data}" + ) + + return json.dumps(user_data) + + +@app.route("/api/atm/", methods=["POST"]) +def get_atms(): + logging.debug("=========================> get atms called") + + atm_locator_host = os.getenv("ATM_LOCATOR_HOST", "localhost") + logging.debug( + f"=========================> forwarding to {atm_locator_host}:8001/api/atm" + ) + + atm_data = flask_client_requests.post( + f"http://{atm_locator_host}:8001/api/atm", json=request.json + ).json() + logging.debug( + f"=========================> response from {atm_locator_host}:8001/api/atm: {atm_data}" + ) + + return json.dumps(atm_data) + + +@app.route("/api/atm/", methods=["GET"]) +def get_specific_atm(id): + logging.debug("=========================> get specific atm called") + + atm_locator_host = os.getenv("ATM_LOCATOR_HOST", "localhost") + logging.debug( + f"=========================> forwarding to {atm_locator_host}:8001/api/atm/{id}" + ) + + atm_data = flask_client_requests.get( + f"http://{atm_locator_host}:8001/api/atm/{id}" + ).json() + logging.debug( + f"=========================> response from {atm_locator_host}:8001/api/atm/{id}: {atm_data}" + ) + + return json.dumps(atm_data) + + +if __name__ == "__main__": + app.run(debug=True) diff --git a/dashboard/loan_pb2.py b/dashboard/loan_pb2.py new file mode 100644 index 0000000..92e32f9 --- /dev/null +++ b/dashboard/loan_pb2.py @@ -0,0 +1,39 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: loan.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nloan.proto\"\xda\x01\n\x0bLoanRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x03 \x01(\t\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x04 \x01(\t\x12\x14\n\x0cgovt_id_type\x18\x05 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x06 \x01(\t\x12\x11\n\tloan_type\x18\x07 \x01(\t\x12\x13\n\x0bloan_amount\x18\x08 \x01(\x01\x12\x15\n\rinterest_rate\x18\t \x01(\x01\x12\x13\n\x0btime_period\x18\n \x01(\t\"1\n\x0cLoanResponse\x12\x10\n\x08\x61pproved\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"$\n\x13LoansHistoryRequest\x12\r\n\x05\x65mail\x18\x01 \x01(\t\"\xf6\x01\n\x04Loan\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x03 \x01(\t\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x04 \x01(\t\x12\x14\n\x0cgovt_id_type\x18\x05 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x06 \x01(\t\x12\x11\n\tloan_type\x18\x07 \x01(\t\x12\x13\n\x0bloan_amount\x18\x08 \x01(\x01\x12\x15\n\rinterest_rate\x18\t \x01(\x01\x12\x13\n\x0btime_period\x18\n \x01(\t\x12\x0e\n\x06status\x18\x0b \x01(\t\x12\x11\n\ttimestamp\x18\x0c \x01(\t\",\n\x14LoansHistoryResponse\x12\x14\n\x05loans\x18\x01 \x03(\x0b\x32\x05.Loan2\x7f\n\x0bLoanService\x12\x31\n\x12ProcessLoanRequest\x12\x0c.LoanRequest\x1a\r.LoanResponse\x12=\n\x0egetLoanHistory\x12\x14.LoansHistoryRequest\x1a\x15.LoansHistoryResponseb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'loan_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _LOANREQUEST._serialized_start=15 + _LOANREQUEST._serialized_end=233 + _LOANRESPONSE._serialized_start=235 + _LOANRESPONSE._serialized_end=284 + _LOANSHISTORYREQUEST._serialized_start=286 + _LOANSHISTORYREQUEST._serialized_end=322 + _LOAN._serialized_start=325 + _LOAN._serialized_end=571 + _LOANSHISTORYRESPONSE._serialized_start=573 + _LOANSHISTORYRESPONSE._serialized_end=617 + _LOANSERVICE._serialized_start=619 + _LOANSERVICE._serialized_end=746 +# @@protoc_insertion_point(module_scope) diff --git a/dashboard/loan_pb2_grpc.py b/dashboard/loan_pb2_grpc.py new file mode 100644 index 0000000..4a73bc3 --- /dev/null +++ b/dashboard/loan_pb2_grpc.py @@ -0,0 +1,103 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import loan_pb2 as loan__pb2 + + +class LoanServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ProcessLoanRequest = channel.unary_unary( + '/LoanService/ProcessLoanRequest', + request_serializer=loan__pb2.LoanRequest.SerializeToString, + response_deserializer=loan__pb2.LoanResponse.FromString, + ) + self.getLoanHistory = channel.unary_unary( + '/LoanService/getLoanHistory', + request_serializer=loan__pb2.LoansHistoryRequest.SerializeToString, + response_deserializer=loan__pb2.LoansHistoryResponse.FromString, + ) + + +class LoanServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def ProcessLoanRequest(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getLoanHistory(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_LoanServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ProcessLoanRequest': grpc.unary_unary_rpc_method_handler( + servicer.ProcessLoanRequest, + request_deserializer=loan__pb2.LoanRequest.FromString, + response_serializer=loan__pb2.LoanResponse.SerializeToString, + ), + 'getLoanHistory': grpc.unary_unary_rpc_method_handler( + servicer.getLoanHistory, + request_deserializer=loan__pb2.LoansHistoryRequest.FromString, + response_serializer=loan__pb2.LoansHistoryResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'LoanService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class LoanService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ProcessLoanRequest(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/LoanService/ProcessLoanRequest', + loan__pb2.LoanRequest.SerializeToString, + loan__pb2.LoanResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getLoanHistory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/LoanService/getLoanHistory', + loan__pb2.LoansHistoryRequest.SerializeToString, + loan__pb2.LoansHistoryResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/dashboard/requirements.txt b/dashboard/requirements.txt new file mode 100644 index 0000000..9e25d54 --- /dev/null +++ b/dashboard/requirements.txt @@ -0,0 +1,23 @@ +attrs==23.1.0 +click==7.1.2 +dnspython==2.3.0 +Flask==1.1.4 +Flask-Cors==3.0.10 +grpcio==1.54.2 +grpcio-tools==1.54.2 +itsdangerous==1.1.0 +Jinja2==2.11.3 +MarkupSafe==2.0.1 +more-itertools==9.1.0 +packaging==23.1 +pluggy==0.13.1 +protobuf==4.23.2 +py==1.11.0 +pymongo==4.3.3 +pytest==5.4.3 +six==1.16.0 +wcwidth==0.2.6 +Werkzeug==1.0.1 +requests +python-dotenv==1.0.0 +dotmap \ No newline at end of file diff --git a/dashboard/templates/create_account_form.html b/dashboard/templates/create_account_form.html new file mode 100644 index 0000000..84db419 --- /dev/null +++ b/dashboard/templates/create_account_form.html @@ -0,0 +1,33 @@ + + + + + + Create Account Form + + +

Create Account Form

+
+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ + +
+ + diff --git a/dashboard/templates/detail_form.html b/dashboard/templates/detail_form.html new file mode 100644 index 0000000..be2ee85 --- /dev/null +++ b/dashboard/templates/detail_form.html @@ -0,0 +1,20 @@ + + + + + + Account Details Request Form + + +

Account Details Request Form

+
+ +

+ +
+ + diff --git a/dashboard/templates/detail_result.html b/dashboard/templates/detail_result.html new file mode 100644 index 0000000..1e7e497 --- /dev/null +++ b/dashboard/templates/detail_result.html @@ -0,0 +1,19 @@ + + + + + + Account Details Result + + +

Account Details Result

+

Account Number: {{ response.account.account_number }}

+

Account Holder Name: {{ response.account.account_holder_name }}

+

Balance: {{ response.account.balance }}

+

Currency: {{ response.account.currency }}

+ + diff --git a/dashboard/templates/homepage.html b/dashboard/templates/homepage.html new file mode 100644 index 0000000..f5d88e9 --- /dev/null +++ b/dashboard/templates/homepage.html @@ -0,0 +1,19 @@ + + + + + + + Banking App + + +

Banking App

+
    +
  • {{ account }}
  • +
+ + \ No newline at end of file diff --git a/dashboard/templates/loan_form.html b/dashboard/templates/loan_form.html new file mode 100644 index 0000000..6c8b74c --- /dev/null +++ b/dashboard/templates/loan_form.html @@ -0,0 +1,22 @@ + + + + + + Loan Request Form + + +

Loan Request Form

+
+ +

+ +

+ +
+ + diff --git a/dashboard/templates/loan_result.html b/dashboard/templates/loan_result.html new file mode 100644 index 0000000..b3dd55a --- /dev/null +++ b/dashboard/templates/loan_result.html @@ -0,0 +1,18 @@ + + + + + + Loan Request Result + + +

Loan Request Result

+ +

Loan Approved: {{ response.approved }}

+ + + diff --git a/dashboard/templates/transaction.html b/dashboard/templates/transaction.html new file mode 100644 index 0000000..9820222 --- /dev/null +++ b/dashboard/templates/transaction.html @@ -0,0 +1,27 @@ + + + + + + Transaction Form + + +

Transaction Form

+
+ +
+ + +
+ + +
+ + +
+ + diff --git a/dashboard/transaction_pb2.py b/dashboard/transaction_pb2.py new file mode 100644 index 0000000..1696859 --- /dev/null +++ b/dashboard/transaction_pb2.py @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: transaction.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11transaction.proto\"\xb0\x01\n\x12TransactionRequest\x12\x1d\n\x15sender_account_number\x18\x01 \x01(\t\x12\x1b\n\x13sender_account_type\x18\x02 \x01(\t\x12\x1f\n\x17receiver_account_number\x18\x03 \x01(\t\x12\x1d\n\x15receiver_account_type\x18\x04 \x01(\t\x12\x0e\n\x06\x61mount\x18\x05 \x01(\x01\x12\x0e\n\x06reason\x18\x06 \x01(\t\"8\n\x13TransactionResponse\x12\x10\n\x08\x61pproved\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"3\n\x19GetALLTransactionsRequest\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\"\x7f\n\x0bTransaction\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x01\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\ntime_stamp\x18\x04 \x01(\t\x12\x0c\n\x04type\x18\x05 \x01(\t\x12\x16\n\x0etransaction_id\x18\x06 \x01(\t\"@\n\x1aGetALLTransactionsResponse\x12\"\n\x0ctransactions\x18\x01 \x03(\x0b\x32\x0c.Transaction\"\\\n\x0cZelleRequest\x12\x14\n\x0csender_email\x18\x01 \x01(\t\x12\x16\n\x0ereceiver_email\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x01\x12\x0e\n\x06reason\x18\x04 \x01(\t\"0\n\x16TransactionByIDRequest\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t2\x8a\x02\n\x12TransactionService\x12\x36\n\tsendMoney\x12\x13.TransactionRequest\x1a\x14.TransactionResponse\x12Q\n\x16getTransactionsHistory\x12\x1a.GetALLTransactionsRequest\x1a\x1b.GetALLTransactionsResponse\x12,\n\x05Zelle\x12\r.ZelleRequest\x1a\x14.TransactionResponse\x12;\n\x12getTransactionByID\x12\x17.TransactionByIDRequest\x1a\x0c.Transactionb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'transaction_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _TRANSACTIONREQUEST._serialized_start=22 + _TRANSACTIONREQUEST._serialized_end=198 + _TRANSACTIONRESPONSE._serialized_start=200 + _TRANSACTIONRESPONSE._serialized_end=256 + _GETALLTRANSACTIONSREQUEST._serialized_start=258 + _GETALLTRANSACTIONSREQUEST._serialized_end=309 + _TRANSACTION._serialized_start=311 + _TRANSACTION._serialized_end=438 + _GETALLTRANSACTIONSRESPONSE._serialized_start=440 + _GETALLTRANSACTIONSRESPONSE._serialized_end=504 + _ZELLEREQUEST._serialized_start=506 + _ZELLEREQUEST._serialized_end=598 + _TRANSACTIONBYIDREQUEST._serialized_start=600 + _TRANSACTIONBYIDREQUEST._serialized_end=648 + _TRANSACTIONSERVICE._serialized_start=651 + _TRANSACTIONSERVICE._serialized_end=917 +# @@protoc_insertion_point(module_scope) diff --git a/dashboard/transaction_pb2_grpc.py b/dashboard/transaction_pb2_grpc.py new file mode 100644 index 0000000..0fedc4d --- /dev/null +++ b/dashboard/transaction_pb2_grpc.py @@ -0,0 +1,169 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import transaction_pb2 as transaction__pb2 + + +class TransactionServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.sendMoney = channel.unary_unary( + '/TransactionService/sendMoney', + request_serializer=transaction__pb2.TransactionRequest.SerializeToString, + response_deserializer=transaction__pb2.TransactionResponse.FromString, + ) + self.getTransactionsHistory = channel.unary_unary( + '/TransactionService/getTransactionsHistory', + request_serializer=transaction__pb2.GetALLTransactionsRequest.SerializeToString, + response_deserializer=transaction__pb2.GetALLTransactionsResponse.FromString, + ) + self.Zelle = channel.unary_unary( + '/TransactionService/Zelle', + request_serializer=transaction__pb2.ZelleRequest.SerializeToString, + response_deserializer=transaction__pb2.TransactionResponse.FromString, + ) + self.getTransactionByID = channel.unary_unary( + '/TransactionService/getTransactionByID', + request_serializer=transaction__pb2.TransactionByIDRequest.SerializeToString, + response_deserializer=transaction__pb2.Transaction.FromString, + ) + + +class TransactionServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def sendMoney(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getTransactionsHistory(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Zelle(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getTransactionByID(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TransactionServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'sendMoney': grpc.unary_unary_rpc_method_handler( + servicer.sendMoney, + request_deserializer=transaction__pb2.TransactionRequest.FromString, + response_serializer=transaction__pb2.TransactionResponse.SerializeToString, + ), + 'getTransactionsHistory': grpc.unary_unary_rpc_method_handler( + servicer.getTransactionsHistory, + request_deserializer=transaction__pb2.GetALLTransactionsRequest.FromString, + response_serializer=transaction__pb2.GetALLTransactionsResponse.SerializeToString, + ), + 'Zelle': grpc.unary_unary_rpc_method_handler( + servicer.Zelle, + request_deserializer=transaction__pb2.ZelleRequest.FromString, + response_serializer=transaction__pb2.TransactionResponse.SerializeToString, + ), + 'getTransactionByID': grpc.unary_unary_rpc_method_handler( + servicer.getTransactionByID, + request_deserializer=transaction__pb2.TransactionByIDRequest.FromString, + response_serializer=transaction__pb2.Transaction.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'TransactionService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class TransactionService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def sendMoney(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/sendMoney', + transaction__pb2.TransactionRequest.SerializeToString, + transaction__pb2.TransactionResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getTransactionsHistory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/getTransactionsHistory', + transaction__pb2.GetALLTransactionsRequest.SerializeToString, + transaction__pb2.GetALLTransactionsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Zelle(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/Zelle', + transaction__pb2.ZelleRequest.SerializeToString, + transaction__pb2.TransactionResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getTransactionByID(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/getTransactionByID', + transaction__pb2.TransactionByIDRequest.SerializeToString, + transaction__pb2.Transaction.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..e3d2544 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,140 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +version: "3.9" + +services: + mongo: + image: mongo + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example + networks: + - bankapp-network + + ui: + build: + context: ./ui + container_name: ui + hostname: ui + image: martian-bank-ui + ports: + - "3000:3000" + restart: always + env_file: + - ./ui/.env + networks: + - bankapp-network + + nginx: + build: + context: ./nginx + container_name: nginx + hostname: nginx + image: martian-bank-nginx + ports: + - "8080:8080" + depends_on: + - customer-auth + - atm-locator + - ui + restart: always + networks: + - bankapp-network + + customer-auth: + build: + context: ./customer-auth + container_name: customer-auth + hostname: customer-auth + image: martian-bank-customer-auth + ports: + - "8000:8000" + env_file: + - ./customer-auth/.env + restart: always + networks: + - bankapp-network + + atm-locator: + build: + context: ./atm-locator + container_name: atm-locator + hostname: martian-bank-atm-locator + image: martian-bank-atm-locator + ports: + - "8001:8001" + env_file: + - ./atm-locator/.env + restart: always + networks: + - bankapp-network + + dashboard: + container_name: dashboard + hostname: dashboard + ports: + - "5000:5000" + restart: always + image: martian-bank-dashboard + build: + context: . + dockerfile: dashboard/Dockerfile + environment: + ACCOUNT_HOST: accounts + TRANSACTION_HOST: transactions + LOAN_HOST: loan + networks: + - bankapp-network + + accounts: + build: + context: . + dockerfile: accounts/Dockerfile + container_name: accounts + hostname: accounts + image: martian-bank-accounts + restart: always + networks: + - bankapp-network + + loan: + build: + context: . + dockerfile: loan/Dockerfile + container_name: loan + hostname: loan + image: martian-bank-loan + restart: always + networks: + - bankapp-network + + transactions: + build: + context: . + dockerfile: transactions/Dockerfile + container_name: transactions + hostname: martian-bank-transactions + image: martian-bank-transactions + restart: always + networks: + - bankapp-network + + locust: + build: + context: . + dockerfile: performance_tests/locust/Dockerfile + container_name: locust + hostname: locust + ports: + - "8089:8089" + image: martian-bank-locust + restart: always + networks: + - bankapp-network + +networks: + bankapp-network: + driver: bridge diff --git a/integrations/apiclarity/NOTE.md b/integrations/apiclarity/NOTE.md new file mode 100644 index 0000000..c169a88 --- /dev/null +++ b/integrations/apiclarity/NOTE.md @@ -0,0 +1,5 @@ +# Installing MartianBank with API-Clarity + +For [API Clarity](https://github.com/openclarity/apiclarity) to catch MartianBank's traffic, there are two additional steps that need to be performed. + +Please follow the instructions in ```apiclarity.sh```. \ No newline at end of file diff --git a/integrations/apiclarity/apiclarity.sh b/integrations/apiclarity/apiclarity.sh new file mode 100644 index 0000000..806dbb7 --- /dev/null +++ b/integrations/apiclarity/apiclarity.sh @@ -0,0 +1,52 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +####################################################################################### +## OPTIONAL STEPS: + +# clean all resources from previous deployments + +helm uninstall martianbank -n bank-app +helm uninstall apiclarity -n apiclarity + +kubectl delete all --all -n bank-app +kubectl delete all --all -n apiclarity + +# delete namespaces + +kubectl delete namespace bank-app +kubectl delete namespace apiclarity + +####################################################################################### +## Steps to deploy martian-bank with apiclarity + +# create namespaces + +kubectl create namespace bank-app +kubectl label namespace bank-app istio-injection=enabled + +# install martianbank (without NGINX) + +helm install --set 'nginx.enabled=false' martianbank martianbank -n bank-app & sleep 20 +kubectl get pods -n bank-app +kubectl get services -n bank-app + +# install API Clarity (note the namespace for envoyWasm) + +helm install --set 'trafficSource.envoyWasm.enabled=true' --set 'trafficSource.envoyWasm.namespaces={bank-app}' --set 'supportExternalTraceSource.enabled=true' --create-namespace apiclarity apiclarity/apiclarity -n apiclarity & sleep 20 +kubectl get pods -n apiclarity +kubectl get services -n apiclarity + +# make UI as load balancer +kubectl apply -f ./integrations/apiclarity/lb-apiclarity.yaml -n apiclarity + +# after these steps, run this commmand to set the dashboard IP: +dashboard_external_ip=$(kubectl get services -n bank-app | grep dashboard | awk '{print $4}') +echo $dashboard_external_ip +helm upgrade martianbank martianbank -n bank-app --set 'nginx.enabled=false' --set "nginx.dashboardIP=$dashboard_external_ip" & sleep 40 + +# To generate load using locust +locust_pod=$(kubectl get pods -n bank-app | grep locust | awk '{print $1}') +echo $locust_pod +kubectl exec -it $locust_pod -n bank-app -- /bin/bash -c "bash locust.sh" \ No newline at end of file diff --git a/integrations/apiclarity/lb-apiclarity.yaml b/integrations/apiclarity/lb-apiclarity.yaml new file mode 100644 index 0000000..8536af9 --- /dev/null +++ b/integrations/apiclarity/lb-apiclarity.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +apiVersion: v1 +kind: Service +metadata: + name: lb-apiclarity + labels: + app: apiclarity-apiclarity +spec: + selector: + app: apiclarity-apiclarity + type: LoadBalancer + ports: + - name: http + port: 8080 + targetPort: 8080 + protocol: TCP diff --git a/k8.yaml b/k8.yaml new file mode 100644 index 0000000..180b8b8 --- /dev/null +++ b/k8.yaml @@ -0,0 +1,342 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# You can tell Kubernetes how to identify your microservice by using labels + +# UI +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ui + labels: + app: ui +spec: + replicas: 1 + selector: + matchLabels: + app: ui + template: + metadata: + labels: + app: ui + spec: + containers: + - name: ui + image: docker.io/jashmehta3300/martian-bank-ui + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dashboard + labels: + app: dashboard +spec: + replicas: 1 + selector: + matchLabels: + app: dashboard + template: + metadata: + labels: + app: dashboard + spec: + containers: + - name: dashboard + image: docker.io/waris95/martian-bank-dashboard + env: + - name: ACCOUNT_HOST + value: accounts + - name: LOAN_HOST + value: loan + - name: TRANSACTION_HOST + value: transactions + + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: accounts + labels: + app: accounts +spec: + replicas: 1 + selector: + matchLabels: + app: accounts + template: + metadata: + labels: + app: accounts + spec: + containers: + - name: accounts + image: docker.io/waris95/martian-bank-accounts +# loan +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loan + labels: + app: loan +spec: + replicas: 1 + selector: + matchLabels: + app: loan + template: + metadata: + labels: + app: loan + spec: + containers: + - name: loan + # registry: docker.io + image: docker.io/waris95/martian-bank-loan + +# transactions + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: transactions + labels: + app: transactions +spec: + replicas: 1 + selector: + matchLabels: + app: transactions + template: + metadata: + labels: + app: transactions + spec: + containers: + - name: transactions + image: docker.io/waris95/martian-bank-transactions + + +# customer-auth + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: customer-auth + labels: + app: customer-auth +spec: + replicas: 1 + selector: + matchLabels: + app: customer-auth + template: + metadata: + labels: + app: customer-auth + spec: + containers: + - name: customer-auth + image: docker.io/jashmehta3300/martian-bank-customer-auth + + +# atm-locator +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: atm-locator + labels: + app: atm-locator +spec: + replicas: 1 + selector: + matchLabels: + app: atm-locator + template: + metadata: + labels: + app: atm-locator + spec: + containers: + - name: atm-locator + image: docker.io/jashmehta3300/martian-bank-atm-locator + +# nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: docker.io/jashmehta3300/martian-bank-nginx + + +# make mongodb deployemnt +--- + +apiVersion: v1 +kind: Pod +metadata: + name: mongo-pod +spec: + containers: + - name: mongo + image: mongo + env: + - name: MONGO_INITDB_ROOT_USERNAME + value: root + - name: MONGO_INITDB_ROOT_PASSWORD + value: example + ports: + - containerPort: 27017 + name: mongodb + + + + + + + + + +# Service + +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx +spec: + type: LoadBalancer + selector: + app: nginx + ports: + - protocol: TCP + port: 80 + targetPort: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: customer-auth +spec: + selector: + app: customer-auth + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + +--- +apiVersion: v1 +kind: Service +metadata: + name: atm-locator +spec: + selector: + app: atm-locator + ports: + - protocol: TCP + port: 8001 + targetPort: 8001 + +--- +apiVersion: v1 +kind: Service +metadata: + name: ui +spec: + type: LoadBalancer + selector: + app: ui + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 + +--- +apiVersion: v1 +kind: Service +metadata: + name: customer-auth +spec: + selector: + app: customer-auth + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + + +--- +apiVersion: v1 +kind: Service +metadata: + name: dashboard +spec: + type: LoadBalancer # This is the only line that is different from the previous service that this is accessible from outside + selector: + app: dashboard + ports: + - protocol: TCP + port: 5000 + targetPort: 5000 + +--- +apiVersion: v1 +kind: Service +metadata: + name: accounts +spec: + selector: + app: accounts + ports: + - protocol: TCP + port: 50051 + targetPort: 50051 + +--- +apiVersion: v1 +kind: Service +metadata: + name: loan +spec: + selector: + app: loan + ports: + - protocol: TCP + port: 50053 + targetPort: 50053 + +--- +apiVersion: v1 +kind: Service +metadata: + name: transactions +spec: + selector: + app: transactions + ports: + - protocol: TCP + port: 50052 + targetPort: 50052 diff --git a/licenses/licenses.txt b/licenses/licenses.txt new file mode 100644 index 0000000..0da7936 --- /dev/null +++ b/licenses/licenses.txt @@ -0,0 +1,95 @@ ++--------------------------+-----------------------------+ +| Package | License | ++--------------------------+-----------------------------+ +| altgraph 0.17.2 | MIT | +| attrs 23.1.0 | (Licence not found) | +| blinker 1.6.2 | MIT License | +| certifi 2023.7.22 | MPL-2.0 | +| charset-normalizer 3.2.0 | MIT | +| click 7.1.2 | BSD-3-Clause | +| dnspython 2.3.0 | ISC | +| dotmap 1.3.30 | MIT | +| Flask 1.1.4 | BSD-3-Clause | +| Flask-Cors 3.0.10 | MIT | +| future 0.18.2 | MIT | +| grpcio 1.54.2 | Apache License 2.0 | +| grpcio-tools 1.54.2 | Apache License 2.0 | +| idna 3.4 | (Licence not found) | +| importlib-metadata 6.8.0 | (Licence not found) | +| itsdangerous 1.1.0 | BSD | +| Jinja2 2.11.3 | BSD-3-Clause | +| macholib 1.15.2 | MIT | +| MarkupSafe 2.0.1 | BSD-3-Clause | +| more-itertools 9.1.0 | (Licence not found) | +| packaging 23.1 | (Licence not found) | +| pip 23.2.1 | MIT | +| pip-licenses 4.3.2 | MIT | +| pluggy 0.13.1 | MIT license | +| prettytable 3.8.0 | BSD (3 clause) | +| protobuf 4.23.2 | 3-Clause BSD License | +| py 1.11.0 | MIT license | +| pymongo 4.3.3 | Apache License, Version 2.0 | +| pytest 5.4.3 | MIT license | +| python-dotenv 1.0.0 | BSD-3-Clause | +| requests 2.31.0 | Apache 2.0 | +| setuptools 58.0.4 | UNKNOWN | +| six 1.16.0 | MIT | +| urllib3 2.0.4 | (Licence not found) | +| wcwidth 0.2.6 | MIT | +| Werkzeug 1.0.1 | BSD-3-Clause | +| wheel 0.37.0 | MIT | +| zipp 3.16.2 | (Licence not found) | ++--------------------------+-----------------------------+ +fortawesome/fontawesome-free 6.4.0 (CC-BY-4.0 AND OFL-1.1 AND MIT) +fortawesome/fontawesome-svg-core 6.4.0 MIT +fortawesome/free-regular-svg-icons 6.4.0 (CC-BY-4.0 AND MIT) +fortawesome/free-solid-svg-icons 6.4.0 (CC-BY-4.0 AND MIT) +fortawesome/react-fontawesome 0.2.0 MIT +reduxjs/toolkit 1.9.5 MIT +axios 1.4.0 MIT +babel-plugin-macros 3.1.0 MIT +bootstrap 5.3.0 MIT +google-map-react 2.2.1 MIT +js-cookie 3.0.5 MIT +leaflet 1.9.4 BSD-2-Clause +mdb-react-ui-kit 6.1.0 Custom: +react-bootstrap 2.7.4 MIT +react-dom 18.2.0 MIT +react-icons 4.9.0 MIT +react-leaflet 4.2.1 Hippocratic-2.1 +react-query 3.39.3 MIT +react-redux 8.0.7 MIT +react-router-bootstrap 0.26.2 Apache-2.0 +react-router-dom 6.12.1 MIT +react-toastify 9.1.3 MIT +react 18.2.0 MIT +save 2.9.0 ISC +ui 0.0.0 UNLICENSED +types/swagger-jsdoc 6.0.1 MIT +types/swagger-ui-express 4.1.3 MIT +auth 0.0.9 BSD* +bcryptjs 2.4.3 MIT +colors 1.4.0 MIT +cookie-parser 1.4.6 MIT +cors 2.8.5 MIT +customer-auth 1.0.0 MIT +dotenv 16.1.4 BSD-2-Clause +express-async-handler 1.2.0 MIT +express 4.18.2 MIT +jsonwebtoken 9.0.0 MIT +loadtest 5.2.0 MIT +mongoose 7.2.4 MIT +morgan 1.10.0 MIT +swagger-jsdoc 6.2.8 MIT +swagger-ui-express 4.6.3 MIT +atm-locator 1.0.0 MIT +colors 1.4.0 MIT +cookie-parser 1.4.6 MIT +cors 2.8.5 MIT +dotenv 16.1.4 BSD-2-Clause +express-async-handler 1.2.0 MIT +express 4.18.2 MIT +jsonwebtoken 9.0.0 MIT +mongoose 7.2.4 MIT +morgan 1.10.0 MIT +nodemon 2.0.22 MIT diff --git a/licenses/python_licenses.py b/licenses/python_licenses.py new file mode 100644 index 0000000..06d51a8 --- /dev/null +++ b/licenses/python_licenses.py @@ -0,0 +1,23 @@ +import pkg_resources +import prettytable + +def get_pkg_license(pkg): + try: + lines = pkg.get_metadata_lines('METADATA') + except: + lines = pkg.get_metadata_lines('PKG-INFO') + + for line in lines: + if line.startswith('License:'): + return line[9:] + return '(Licence not found)' + +def print_packages_and_licenses(): + t = prettytable.PrettyTable(['Package', 'License']) + for pkg in sorted(pkg_resources.working_set, key=lambda x: str(x).lower()): + t.add_row((str(pkg), get_pkg_license(pkg))) + print(t) + + +if __name__ == "__main__": + print_packages_and_licenses() \ No newline at end of file diff --git a/licenses/report.sh b/licenses/report.sh new file mode 100644 index 0000000..fb74030 --- /dev/null +++ b/licenses/report.sh @@ -0,0 +1,36 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +####################################################################################### + +## Install packages to scan license (on mac OS) +npm i -g license-checker-rseidelsohn --silent +pip3 install prettytable --quiet +brew install jq --quiet + +####################################################################################### +## Clear existing licenses.txt file (if any) +echo -n > ./licenses.txt +cd .. + +####################################################################################### +## Generate license reports + +# report for Python microservicesxs +python3 python_licenses.py >> ./licenses/licenses.txt + +# report for javascript microservices +cd ui +license-checker-rseidelsohn --json --production --direct | jq -r 'to_entries | map([(.key | split("@"))[-2], (.key | split("@"))[-1], .value.licenses] | @tsv) | .[]' | column -t -s $'\t' >>../licenses/licenses.txt +cd .. + +cd customer-auth +license-checker-rseidelsohn --json --production --direct | jq -r 'to_entries | map([(.key | split("@"))[-2], (.key | split("@"))[-1], .value.licenses] | @tsv) | .[]' | column -t -s $'\t' >>../licenses/licenses.txt +cd .. + +cd atm-locator +license-checker-rseidelsohn --json --production --direct | jq -r 'to_entries | map([(.key | split("@"))[-2], (.key | split("@"))[-1], .value.licenses] | @tsv) | .[]' | column -t -s $'\t' >>../licenses/licenses.txt +cd .. + +echo "License report generated successfully!" \ No newline at end of file diff --git a/loan/Dockerfile b/loan/Dockerfile new file mode 100644 index 0000000..f01cc8a --- /dev/null +++ b/loan/Dockerfile @@ -0,0 +1,18 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 python + +RUN mkdir /service +COPY protobufs/ /service/protobufs/ +COPY loan/ /service/loan/ +WORKDIR /service/loan +RUN python -m pip install --upgrade pip +RUN python -m pip install -r requirements.txt + +RUN python -m grpc_tools.protoc -I ../protobufs --python_out=. --grpc_python_out=. ../protobufs/loan.proto + +EXPOSE 50053 + +ENTRYPOINT ["python", "loan.py"] diff --git a/loan/client_api.py b/loan/client_api.py new file mode 100644 index 0000000..f4e7aa7 --- /dev/null +++ b/loan/client_api.py @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +import grpc + +from loan_pb2_grpc import LoanServiceStub +from loan_pb2 import LoanRequest + +channel = grpc.insecure_channel('localhost:50053') +client = LoanServiceStub(channel) + +reuest = LoanRequest(account_number="3", amount=10.0) + +response = client.ProcessLoanRequest(reuest) + +print(response) \ No newline at end of file diff --git a/loan/loan.py b/loan/loan.py new file mode 100644 index 0000000..1f57a89 --- /dev/null +++ b/loan/loan.py @@ -0,0 +1,229 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from concurrent import futures +import random +import datetime +import os +import grpc + +import logging +from flask import Flask, request, jsonify +# set logging to debug +logging.basicConfig(level=logging.DEBUG) + + +from loan_pb2 import * +import loan_pb2_grpc + +from pymongo.mongo_client import MongoClient + +from dotenv import load_dotenv +load_dotenv() + + +# db_host = os.getenv("DATABASE_HOST", "localhost") +db_url = os.getenv("DB_URL") +if db_url is None: + raise Exception("DB_URL environment variable is not set") + +uri = db_url + +protocol = os.getenv('SERVICE_PROTOCOL', 'http') +if protocol is None: + raise Exception("SERVICE_PROTOCOL environment variable is not set") + +protocol = protocol.lower() + +logging.debug(f"microservice protocol: {protocol}") + + + +client = MongoClient(uri) +db = client["bank"] +collection_accounts = db["accounts"] +collection_loans = db["loans"] + +class LoanGeneric: + def ProcessLoanRequest(self, request_data): + name = request_data["name"] + email = request_data["email"] + account_type = request_data["account_type"] + account_number = request_data["account_number"] + govt_id_type = request_data["govt_id_type"] + govt_id_number = request_data["govt_id_number"] + loan_type = request_data["loan_type"] + loan_amount = float(request_data["loan_amount"]) + interest_rate = float(request_data["interest_rate"]) + time_period = request_data["time_period"] + user_account = self.__getAccount(account_number) + + # count = collection_loans.count_documents({"email_id": email, 'account_number': account_number}) + count = collection_accounts.count_documents({"email_id": email, 'account_number': account_number}) + + logging.debug(f"user account only based on account number search : {user_account}") + logging.debug(f"Count whther the email and account exist or not : {count}") + if count == 0: + return {"approved": False, "message": "Email or Account number not found."} + result = self.__approveLoan(user_account, loan_amount) + logging.debug(f"Result {result}") + message = "Loan Approved" if result else "Loan Rejected" + + # insert loan request into db + loan_request = { + "name": name, + "email": email, + "account_type": account_type, + "account_number": account_number, + "govt_id_type": govt_id_type, + "govt_id_number": govt_id_number, + "loan_type": loan_type, + "loan_amount": loan_amount, + "interest_rate": interest_rate, + "time_period": time_period, + "status": "Declined", + "timestamp": datetime.datetime.now(), + } + loan_request["status"] = "Approved" if result else "Declined" + + collection_loans.insert_one(loan_request) + + response = {"approved": result, "message": message} + logging.debug(f"Account: {account_number}") + logging.debug(f"Response: {response}") + return response + + def getLoanHistory(self, request_data): + email = request_data["email"] + loans = collection_loans.find({"email": email}) + loan_history = [] + + for l in loans: + loan_history.append( + { + "name": l["name"], + "email": l["email"], + "account_type": l["account_type"], + "account_number": l["account_number"], + "govt_id_type": l["govt_id_type"], + "govt_id_number": l["govt_id_number"], + "loan_type": l["loan_type"], + "loan_amount": l["loan_amount"], + "interest_rate": l["interest_rate"], + "time_period": l["time_period"], + "status": l["status"], + "timestamp": f"{l['timestamp']}", + } + ) + + return loan_history + + def __getAccount(self, account_num): + r = None + accounts = collection_accounts.find() + for acc in accounts: + if acc["account_number"] == account_num: + r = acc + break + # logging.debug(f"Account {r}") + return r + + def __approveLoan(self, account, amount): + if amount < 1: + return False + + account["balance"] += amount + + collection_accounts.update_one( + {"account_number": account["account_number"]}, + {"$set": {"balance": account["balance"]}}, + ) + + return True + +class LoanService(loan_pb2_grpc.LoanServiceServicer): + def __init__(self) -> None: + super().__init__() + # enable github copiolot + self.loan = LoanGeneric() + + def ProcessLoanRequest(self, request, context): + name = request.name + email = request.email + account_type = request.account_type + account_number = request.account_number + govt_id_type = request.govt_id_type + govt_id_number = request.govt_id_number + loan_type = request.loan_type + loan_amount = float(request.loan_amount) + interest_rate = float(request.interest_rate) + time_period = request.time_period + + req = {'name': name, 'email': email, 'account_type': account_type, 'account_number': account_number, 'govt_id_type': govt_id_type, 'govt_id_number': govt_id_number, 'loan_type': loan_type, 'loan_amount': loan_amount, 'interest_rate': interest_rate, 'time_period': time_period} + resutl = self.loan.ProcessLoanRequest(req) + + response = LoanResponse(approved=resutl['approved'], message=resutl['message']) + return response + + def getLoanHistory(self, request, context): + + email = request.email + req = {'email': email} + loan_history = [] + + loans = self.loan.getLoanHistory(req) + + for l in loans: + loan_history.append(Loan(name=l['name'], email=l['email'], account_type=l['account_type'], account_number=l['account_number'], govt_id_type=l['govt_id_type'], govt_id_number=l['govt_id_number'], loan_type=l['loan_type'], loan_amount=l['loan_amount'], interest_rate=l['interest_rate'], time_period=l['time_period'], status=l['status'], timestamp=f"{l['timestamp']}")) + + return LoansHistoryResponse(loans=loan_history) + + + + +app = Flask(__name__) +loan_generic = LoanGeneric() +@app.route("/loan/request", methods=["POST"]) +def process_loan_request(): + request_data = request.json + logging.debug(f"Request: {request_data}") + response = loan_generic.ProcessLoanRequest(request_data) + return jsonify(response) + + +@app.route("/loan/history", methods=["POST"]) +def get_loan_history(): + logging.debug("----------------> Request: /loan/history") + d = request.json + logging.debug(f"Request: {d}") + response = loan_generic.getLoanHistory({"email": d['email']}) + return jsonify(response) + + + + +def serverGRPC(port): + logging.debug(f"Starting GRPC server on port {port}") + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + loan_pb2_grpc.add_LoanServiceServicer_to_server(LoanService(), server) + server.add_insecure_port(f"[::]:{port}") + server.start() + server.wait_for_termination() + +def serverFlask(port): + logging.debug(f"Starting Flask server on port {port}") + app.run(host='0.0.0.0' ,port=port, debug=True) + + +if __name__ == "__main__": + port = 50053 + + if protocol == "grpc": + serverGRPC(port) + else: + serverFlask(port) + + + + diff --git a/loan/loan_pb2.py b/loan/loan_pb2.py new file mode 100644 index 0000000..92e32f9 --- /dev/null +++ b/loan/loan_pb2.py @@ -0,0 +1,39 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: loan.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nloan.proto\"\xda\x01\n\x0bLoanRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x03 \x01(\t\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x04 \x01(\t\x12\x14\n\x0cgovt_id_type\x18\x05 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x06 \x01(\t\x12\x11\n\tloan_type\x18\x07 \x01(\t\x12\x13\n\x0bloan_amount\x18\x08 \x01(\x01\x12\x15\n\rinterest_rate\x18\t \x01(\x01\x12\x13\n\x0btime_period\x18\n \x01(\t\"1\n\x0cLoanResponse\x12\x10\n\x08\x61pproved\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"$\n\x13LoansHistoryRequest\x12\r\n\x05\x65mail\x18\x01 \x01(\t\"\xf6\x01\n\x04Loan\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63ount_type\x18\x03 \x01(\t\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x04 \x01(\t\x12\x14\n\x0cgovt_id_type\x18\x05 \x01(\t\x12\x16\n\x0egovt_id_number\x18\x06 \x01(\t\x12\x11\n\tloan_type\x18\x07 \x01(\t\x12\x13\n\x0bloan_amount\x18\x08 \x01(\x01\x12\x15\n\rinterest_rate\x18\t \x01(\x01\x12\x13\n\x0btime_period\x18\n \x01(\t\x12\x0e\n\x06status\x18\x0b \x01(\t\x12\x11\n\ttimestamp\x18\x0c \x01(\t\",\n\x14LoansHistoryResponse\x12\x14\n\x05loans\x18\x01 \x03(\x0b\x32\x05.Loan2\x7f\n\x0bLoanService\x12\x31\n\x12ProcessLoanRequest\x12\x0c.LoanRequest\x1a\r.LoanResponse\x12=\n\x0egetLoanHistory\x12\x14.LoansHistoryRequest\x1a\x15.LoansHistoryResponseb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'loan_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _LOANREQUEST._serialized_start=15 + _LOANREQUEST._serialized_end=233 + _LOANRESPONSE._serialized_start=235 + _LOANRESPONSE._serialized_end=284 + _LOANSHISTORYREQUEST._serialized_start=286 + _LOANSHISTORYREQUEST._serialized_end=322 + _LOAN._serialized_start=325 + _LOAN._serialized_end=571 + _LOANSHISTORYRESPONSE._serialized_start=573 + _LOANSHISTORYRESPONSE._serialized_end=617 + _LOANSERVICE._serialized_start=619 + _LOANSERVICE._serialized_end=746 +# @@protoc_insertion_point(module_scope) diff --git a/loan/loan_pb2_grpc.py b/loan/loan_pb2_grpc.py new file mode 100644 index 0000000..4a73bc3 --- /dev/null +++ b/loan/loan_pb2_grpc.py @@ -0,0 +1,103 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import loan_pb2 as loan__pb2 + + +class LoanServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ProcessLoanRequest = channel.unary_unary( + '/LoanService/ProcessLoanRequest', + request_serializer=loan__pb2.LoanRequest.SerializeToString, + response_deserializer=loan__pb2.LoanResponse.FromString, + ) + self.getLoanHistory = channel.unary_unary( + '/LoanService/getLoanHistory', + request_serializer=loan__pb2.LoansHistoryRequest.SerializeToString, + response_deserializer=loan__pb2.LoansHistoryResponse.FromString, + ) + + +class LoanServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def ProcessLoanRequest(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getLoanHistory(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_LoanServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ProcessLoanRequest': grpc.unary_unary_rpc_method_handler( + servicer.ProcessLoanRequest, + request_deserializer=loan__pb2.LoanRequest.FromString, + response_serializer=loan__pb2.LoanResponse.SerializeToString, + ), + 'getLoanHistory': grpc.unary_unary_rpc_method_handler( + servicer.getLoanHistory, + request_deserializer=loan__pb2.LoansHistoryRequest.FromString, + response_serializer=loan__pb2.LoansHistoryResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'LoanService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class LoanService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ProcessLoanRequest(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/LoanService/ProcessLoanRequest', + loan__pb2.LoanRequest.SerializeToString, + loan__pb2.LoanResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getLoanHistory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/LoanService/getLoanHistory', + loan__pb2.LoansHistoryRequest.SerializeToString, + loan__pb2.LoansHistoryResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/loan/requirements.txt b/loan/requirements.txt new file mode 100644 index 0000000..419f80d --- /dev/null +++ b/loan/requirements.txt @@ -0,0 +1,23 @@ +attrs==23.1.0 +click==7.1.2 +dnspython==2.3.0 +Flask==1.1.4 +Flask-Cors==3.0.10 +grpcio==1.54.2 +grpcio-tools==1.54.2 +itsdangerous==1.1.0 +Jinja2==2.11.3 +MarkupSafe==2.0.1 +more-itertools==9.1.0 +packaging==23.1 +pluggy==0.13.1 +protobuf==4.23.2 +py==1.11.0 +pymongo==4.3.3 +pytest==5.4.3 +six==1.16.0 +wcwidth==0.2.6 +Werkzeug==1.0.1 +requests +dotmap +python-dotenv==1.0.0 diff --git a/martianbank/.helmignore b/martianbank/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/martianbank/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/martianbank/Chart.yaml b/martianbank/Chart.yaml new file mode 100644 index 0000000..fb42a6d --- /dev/null +++ b/martianbank/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: martianbank +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/martianbank/templates/NOTES.txt b/martianbank/templates/NOTES.txt new file mode 100644 index 0000000..ad65fcf --- /dev/null +++ b/martianbank/templates/NOTES.txt @@ -0,0 +1 @@ +Installation is successful. \ No newline at end of file diff --git a/martianbank/templates/_helpers.tpl b/martianbank/templates/_helpers.tpl new file mode 100644 index 0000000..032de62 --- /dev/null +++ b/martianbank/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "martianbank.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "martianbank.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "martianbank.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "martianbank.labels" -}} +helm.sh/chart: {{ include "martianbank.chart" . }} +{{ include "martianbank.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "martianbank.selectorLabels" -}} +app.kubernetes.io/name: {{ include "martianbank.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "martianbank.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "martianbank.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/martianbank/templates/accounts.yaml b/martianbank/templates/accounts.yaml new file mode 100644 index 0000000..132b44e --- /dev/null +++ b/martianbank/templates/accounts.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: accounts + labels: + app: accounts +spec: + replicas: 1 + selector: + matchLabels: + app: accounts + template: + metadata: + labels: + app: accounts + spec: + containers: + - name: accounts + image: docker.io/waris95/martian-bank-accounts + envFrom: + - configMapRef: + name: configmap-martianbank + env: + - name: SERVICE_PROTOCOL + value: {{.Values.SERVICE_PROTOCOL}} +--- +apiVersion: v1 +kind: Service +metadata: + name: accounts +spec: + selector: + app: accounts + ports: + - protocol: TCP + port: 50051 + targetPort: 50051 diff --git a/martianbank/templates/configmap.yaml b/martianbank/templates/configmap.yaml new file mode 100644 index 0000000..383b583 --- /dev/null +++ b/martianbank/templates/configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-martianbank +data: + DB_URL: "mongodb://root:example@mongodb:27017" + JWT_SECRET: "" +# Set any string as a JWT_SECRET for generating and verifying JWT tokens. \ No newline at end of file diff --git a/martianbank/templates/deployment.yaml b/martianbank/templates/deployment.yaml new file mode 100644 index 0000000..46f1fdb --- /dev/null +++ b/martianbank/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "martianbank.fullname" . }} + labels: + {{- include "martianbank.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "martianbank.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "martianbank.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "martianbank.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/martianbank/templates/hpa.yaml b/martianbank/templates/hpa.yaml new file mode 100644 index 0000000..8b90163 --- /dev/null +++ b/martianbank/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "martianbank.fullname" . }} + labels: + {{- include "martianbank.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "martianbank.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/martianbank/templates/ingress.yaml b/martianbank/templates/ingress.yaml new file mode 100644 index 0000000..d9fcac3 --- /dev/null +++ b/martianbank/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "martianbank.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "martianbank.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/martianbank/templates/k8.yaml b/martianbank/templates/k8.yaml new file mode 100644 index 0000000..1f10414 --- /dev/null +++ b/martianbank/templates/k8.yaml @@ -0,0 +1,293 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ui + labels: + app: ui +spec: + replicas: 1 + selector: + matchLabels: + app: ui + template: + metadata: + labels: + app: ui + spec: + containers: + - name: ui + image: docker.io/jashmehta3300/martian-bank-ui + env: + {{- if .Values.nginx.enabled }} + - name: VITE_USERS_URL + value: /api/users + - name: VITE_ATM_URL + value: /api/atm + - name: VITE_ACCOUNTS_URL + value: /api/account + - name: VITE_TRANSFER_URL + value: /api/transaction + - name: VITE_LOAN_URL + value: /api/loan + {{- else }} + - name: VITE_USERS_URL + value: http://{{ .Values.nginx.dashboardIP }}:5000/api/users + - name: VITE_ATM_URL + value: http://{{ .Values.nginx.dashboardIP }}:5000/api/atm/ + - name: VITE_ACCOUNTS_URL + value: http://{{ .Values.nginx.dashboardIP }}:5000/account/ + - name: VITE_TRANSFER_URL + value: http://{{ .Values.nginx.dashboardIP }}:5000/transaction/ + - name: VITE_LOAN_URL + value: http://{{ .Values.nginx.dashboardIP }}:5000/loan/ + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: ui +spec: + {{- if not .Values.nginx.enabled }} + type: LoadBalancer + {{- end }} + selector: + app: ui + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 + + + +# dashboard +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dashboard + labels: + app: dashboard +spec: + replicas: 1 + selector: + matchLabels: + app: dashboard + template: + metadata: + labels: + app: dashboard + spec: + containers: + - name: dashboard + image: docker.io/jashmehta3300/martian-bank-dashboard + env: + - name: SERVICE_PROTOCOL + value: {{.Values.SERVICE_PROTOCOL}} + - name: ACCOUNT_HOST + value: accounts + - name: LOAN_HOST + value: loan + - name: TRANSACTION_HOST + value: transactions + - name: CUSTOMER_AUTH_HOST + value: customer-auth + - name: ATM_LOCATOR_HOST + value: atm-locator + envFrom: + - configMapRef: + name: configmap-martianbank +--- +apiVersion: v1 +kind: Service +metadata: + name: dashboard +spec: + type: LoadBalancer + selector: + app: dashboard + ports: + - protocol: TCP + port: 5000 + targetPort: 5000 + + + +# customer-auth +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: customer-auth + labels: + app: customer-auth +spec: + replicas: 1 + selector: + matchLabels: + app: customer-auth + template: + metadata: + labels: + app: customer-auth + spec: + containers: + - name: customer-auth + image: docker.io/jashmehta3300/martian-bank-customer-auth + env: + {{- if not .Values.mongodb.enabled }} + - name: DATABASE_HOST + value: "root:example@mongodb" + {{- end }} + - name: NODE_ENV + value: production + - name: PORT + value: "8000" + envFrom: + - configMapRef: + name: configmap-martianbank +--- +apiVersion: v1 +kind: Service +metadata: + name: customer-auth +spec: + selector: + app: customer-auth + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + + + +# atm-locator +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: atm-locator + labels: + app: atm-locator +spec: + replicas: 1 + selector: + matchLabels: + app: atm-locator + template: + metadata: + labels: + app: atm-locator + spec: + containers: + - name: atm-locator + image: docker.io/jashmehta3300/martian-bank-atm-locator + env: + {{- if not .Values.mongodb.enabled }} + - name: DATABASE_HOST + value: "root:example@mongodb" + {{- end }} + - name: NODE_ENV + value: production + - name: PORT + value: "8001" + envFrom: + - configMapRef: + name: configmap-martianbank +--- +apiVersion: v1 +kind: Service +metadata: + name: atm-locator +spec: + selector: + app: atm-locator + ports: + - protocol: TCP + port: 8001 + targetPort: 8001 + + + + +{{- if .Values.nginx.enabled }} +# nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: docker.io/jashmehta3300/martian-bank-nginx +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx +spec: + type: LoadBalancer + selector: + app: nginx + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 +{{- end }} + + +# locust +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: locust + labels: + app: locust +spec: + replicas: 1 + selector: + matchLabels: + app: locust + template: + metadata: + labels: + app: locust + spec: + containers: + - name: locust + image: docker.io/jashmehta3300/martian-bank-locust + env: + - name: VITE_USERS_URL + value: http://customer-auth:8000/api/users + - name: VITE_ATM_URL + value: http://atm-locator:8001/api/atm + - name: VITE_ACCOUNTS_URL + value: http://dashboard:5000/account + - name: VITE_TRANSFER_URL + value: http://dashboard:5000/transaction + - name: VITE_LOAN_URL + value: http://dashboard:5000/loan +--- +apiVersion: v1 +kind: Service +metadata: + name: locust +spec: + selector: + app: locust + ports: + - protocol: TCP + port: 8098 + targetPort: 8098 \ No newline at end of file diff --git a/martianbank/templates/loan.yaml b/martianbank/templates/loan.yaml new file mode 100644 index 0000000..83c23aa --- /dev/null +++ b/martianbank/templates/loan.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loan + labels: + app: loan +spec: + replicas: 1 + selector: + matchLabels: + app: loan + template: + metadata: + labels: + app: loan + spec: + containers: + - name: loan + image: docker.io/waris95/martian-bank-loan + envFrom: + - configMapRef: + name: configmap-martianbank + env: + - name: SERVICE_PROTOCOL + value: {{.Values.SERVICE_PROTOCOL}} + +--- +apiVersion: v1 +kind: Service +metadata: + name: loan +spec: + selector: + app: loan + ports: + - protocol: TCP + port: 50053 + targetPort: 50053 \ No newline at end of file diff --git a/martianbank/templates/mongodb.yaml b/martianbank/templates/mongodb.yaml new file mode 100644 index 0000000..a51610e --- /dev/null +++ b/martianbank/templates/mongodb.yaml @@ -0,0 +1,49 @@ +{{ if not .Values.mongodb.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: mongodb + name: mongodb-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: mongodb + template: + metadata: + labels: + app: mongodb + spec: + containers: + - name: mongodb + volumeMounts: + - mountPath: /data/db + name: mo-data + image: mongo + env: + - name: MONGO_INITDB_ROOT_USERNAME + value: root + - name: MONGO_INITDB_ROOT_PASSWORD + value: example + volumes: + - name: mo-data + persistentVolumeClaim: + claimName: mo-data-pvc + restartPolicy: Always + +# Mongo DB service +--- +apiVersion: v1 +kind: Service +metadata: + name: mongodb +spec: + type: ClusterIP + selector: + app: mongodb + ports: + - protocol: TCP + port: 27017 + targetPort: 27017 +{{ end -}} \ No newline at end of file diff --git a/martianbank/templates/persistentVolume.yaml b/martianbank/templates/persistentVolume.yaml new file mode 100644 index 0000000..e2aa1ea --- /dev/null +++ b/martianbank/templates/persistentVolume.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: mo-data-pv +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 1000Mi + hostPath: + path: /data/standard/default/mo-data-pv + type: "" + persistentVolumeReclaimPolicy: Retain + storageClassName: standard + volumeMode: Filesystem \ No newline at end of file diff --git a/martianbank/templates/persistentVolumeClain.yml b/martianbank/templates/persistentVolumeClain.yml new file mode 100644 index 0000000..24b5ba6 --- /dev/null +++ b/martianbank/templates/persistentVolumeClain.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mo-data-pvc +spec: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1000Mi \ No newline at end of file diff --git a/martianbank/templates/service.yaml b/martianbank/templates/service.yaml new file mode 100644 index 0000000..0258763 --- /dev/null +++ b/martianbank/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "martianbank.fullname" . }} + labels: + {{- include "martianbank.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "martianbank.selectorLabels" . | nindent 4 }} diff --git a/martianbank/templates/serviceaccount.yaml b/martianbank/templates/serviceaccount.yaml new file mode 100644 index 0000000..b4ee3b5 --- /dev/null +++ b/martianbank/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "martianbank.serviceAccountName" . }} + labels: + {{- include "martianbank.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/martianbank/templates/tests/test-connection.yaml b/martianbank/templates/tests/test-connection.yaml new file mode 100644 index 0000000..f574290 --- /dev/null +++ b/martianbank/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "martianbank.fullname" . }}-test-connection" + labels: + {{- include "martianbank.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "martianbank.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/martianbank/templates/transactions.yaml b/martianbank/templates/transactions.yaml new file mode 100644 index 0000000..87d2a26 --- /dev/null +++ b/martianbank/templates/transactions.yaml @@ -0,0 +1,45 @@ +# transactions +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: transactions + labels: + app: transactions +spec: + replicas: 1 + selector: + matchLabels: + app: transactions + template: + metadata: + labels: + app: transactions + spec: + containers: + - name: transactions + image: docker.io/waris95/martian-bank-transactions + envFrom: + - configMapRef: + name: configmap-martianbank + env: + - name: SERVICE_PROTOCOL + value: {{.Values.SERVICE_PROTOCOL}} + + +--- +apiVersion: v1 +kind: Service +metadata: + name: transactions +spec: + selector: + app: transactions + ports: + - protocol: TCP + port: 50052 + targetPort: 50052 + + + + diff --git a/martianbank/values.yaml b/martianbank/values.yaml new file mode 100644 index 0000000..f491bcc --- /dev/null +++ b/martianbank/values.yaml @@ -0,0 +1,112 @@ +# Default values for martianbank. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 85 + +ingress: + enabled: false + className: "" + annotations: + {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +####################################################################################### +## Deploy with/without NGINX + +nginx: + ## For API Clairty, you will have to disable nginx by setting enabled to false + ## + enabled: true + ## If enabled is false then deploy the application, get the external IP of dashboard + ## service, add it here, and apply the changes. (helm upgrade martianbank martianbank) + ## + dashboardIP: "" + + + +####################################################################################### +## Deploy with local MongoDB or with MongoDB Atlas + +mongodb: + ## For easier development and testing, we deploy MongoDB Atlas instance (enabled: true). + ## If you want to use local MongoDB, then set enabled to 'false' + ## + enabled: false + +SERVICE_PROTOCOL: "http" +DB_URL: \ No newline at end of file diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..ef3a867 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,6 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 nginx +COPY ./default.conf /etc/nginx/conf.d/ \ No newline at end of file diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..df88ddc --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,46 @@ +server { + listen 8080; + server_name localhost; + + location / { + proxy_pass http://ui:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api/users { + proxy_pass http://customer-auth:8000/api/users/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api/atm { + proxy_pass http://atm-locator:8001/api/atm/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api/account { + proxy_pass http://dashboard:5000/account/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api/transaction { + proxy_pass http://dashboard:5000/transaction/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api/loan { + proxy_pass http://dashboard:5000/loan/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} \ No newline at end of file diff --git a/performance_tests/locust/Dockerfile b/performance_tests/locust/Dockerfile new file mode 100644 index 0000000..dd9a9a3 --- /dev/null +++ b/performance_tests/locust/Dockerfile @@ -0,0 +1,16 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 python + +RUN mkdir /service +COPY performance_tests/locust/ /service/locust/ +WORKDIR /service/locust +RUN python -m pip install --upgrade pip +RUN python -m pip install -r requirements.txt + +RUN chmod +x locust.sh + +EXPOSE 8089 5557 5558 +ENTRYPOINT [ "sleep", "infinity" ] \ No newline at end of file diff --git a/performance_tests/locust/account_locust.py b/performance_tests/locust/account_locust.py new file mode 100644 index 0000000..6fc13a2 --- /dev/null +++ b/performance_tests/locust/account_locust.py @@ -0,0 +1,56 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from locust import HttpUser, task, SequentialTaskSet, between +from api_urls import ApiUrls +import random +from faker import Faker + +fake = Faker() + + +class MyUser(HttpUser): + host = ApiUrls["VITE_ACCOUNTS_URL"] + + @task + class MyUserTasks(SequentialTaskSet): + wait_time = between(2, 3) + + def on_start(self): + # Create fake account data + self.user_data = { + "name": fake.unique.name(), + "email_id": fake.unique.email(), + "account_type": random.choice( + ["Checking", "Savings", "Money Market", "Investment"] + ), + "government_id_type": random.choice( + ["Driver's License", "Passport", "SSN"] + ), + "govt_id_number": fake.unique.ssn(), + "address": fake.unique.address(), + } + + # Create a new account + self.client.post( + "/create", + data=self.user_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + @task + def get_all_accounts(self): + self.client.post( + "/allaccounts", + data={"email_id": self.user_data["email_id"]}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + @task + def get_particular_account(self): + self.client.get( + "/detail", + data={"email": self.user_data["email_id"]}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) diff --git a/performance_tests/locust/api_urls.py b/performance_tests/locust/api_urls.py new file mode 100644 index 0000000..73b9e53 --- /dev/null +++ b/performance_tests/locust/api_urls.py @@ -0,0 +1,19 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +import os + +VITE_ACCOUNTS_URL = os.getenv("VITE_ACCOUNTS_URL") or 'http://127.0.0.1:5000/account' +VITE_USERS_URL = os.getenv("VITE_USERS_URL") or 'http://localhost:8000/api/users' +VITE_ATM_URL = os.getenv("VITE_ATM_URL") or 'http://localhost:8001/api/atm' +VITE_TRANSFER_URL = os.getenv("VITE_TRANSFER_URL") or 'http://127.0.0.1:5000/transaction' +VITE_LOAN_URL = os.getenv("VITE_LOAN_URL") or 'http://127.0.0.1:5000/loan' + +ApiUrls = { + 'VITE_ACCOUNTS_URL': VITE_ACCOUNTS_URL, + 'VITE_USERS_URL': VITE_USERS_URL, + 'VITE_ATM_URL': VITE_ATM_URL, + 'VITE_TRANSFER_URL': VITE_TRANSFER_URL, + 'VITE_LOAN_URL': VITE_LOAN_URL, +} diff --git a/performance_tests/locust/atm_locust.py b/performance_tests/locust/atm_locust.py new file mode 100644 index 0000000..af48ee5 --- /dev/null +++ b/performance_tests/locust/atm_locust.py @@ -0,0 +1,24 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from locust import HttpUser, task, SequentialTaskSet, between +from api_urls import ApiUrls + + +class MyUser(HttpUser): + host = ApiUrls["VITE_ATM_URL"] + + @task + class MyUserTasks(SequentialTaskSet): + wait_time = between(2, 3) + + @task + def get_all_atms(self): + response = self.client.post("/") + self.atm_data = response.json() + + @task + def get_atm_details(self): + for atm in self.atm_data: + self.client.get(f"/{atm['_id']}") diff --git a/performance_tests/locust/auth_locust.py b/performance_tests/locust/auth_locust.py new file mode 100644 index 0000000..8b8b85e --- /dev/null +++ b/performance_tests/locust/auth_locust.py @@ -0,0 +1,60 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from locust import HttpUser, task, SequentialTaskSet, between +from api_urls import ApiUrls +from faker import Faker + +fake = Faker() + + +class MyUser(HttpUser): + host = ApiUrls["VITE_USERS_URL"] + + @task + class MyUserTasks(SequentialTaskSet): + wait_time = between(2, 3) + + def on_start(self): + # Create fake user data + self.user_data = { + "name": fake.unique.name(), + "email": fake.unique.email(), + "password": fake.unique.password(), + } + + # Register + self.client.post("/", json=self.user_data) + + @task + def login(self): + # Login + self.client.post( + "/auth", + json={ + "email": self.user_data["email"], + "password": self.user_data["password"], + }, + ) + + @task + def get_profile(self): + # Get Profile + self.client.get("/profile", json={"email": self.user_data["email"]}) + + @task + def update_profile(self): + # Update Profile + self.client.put( + "/profile", + json={ + "email": self.user_data["email"], + "password": fake.unique.password(), + }, + ) + + @task + def logout(self): + # Logout + self.client.post("/logout", json={"email": self.user_data["email"]}) \ No newline at end of file diff --git a/performance_tests/locust/loan_locust.py b/performance_tests/locust/loan_locust.py new file mode 100644 index 0000000..b227e06 --- /dev/null +++ b/performance_tests/locust/loan_locust.py @@ -0,0 +1,74 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from locust import HttpUser, task, SequentialTaskSet, between +from api_urls import ApiUrls +import random +from faker import Faker + +fake = Faker() + + +class MyUser(HttpUser): + host = ApiUrls["VITE_LOAN_URL"] + + @task + class MyUserTasks(SequentialTaskSet): + wait_time = between(2, 3) + + def on_start(self): + account_host = ApiUrls["VITE_ACCOUNTS_URL"] + # Create fake account data + self.user_data = { + "name": fake.unique.name(), + "email_id": fake.unique.email(), + "account_type": random.choice( + ["Checking", "Savings", "Money Market", "Investment"] + ), + "government_id_type": random.choice( + ["Driver's License", "Passport", "SSN"] + ), + "govt_id_number": fake.unique.ssn(), + "address": fake.unique.address(), + } + + # Create a new account + self.client.post( + f"{account_host}/create", + data=self.user_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + # Get all accounts + response = self.client.post( + f"{account_host}/allaccounts", + data={"email_id": self.user_data["email_id"]}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + self.account_number = response.json()["response"][0]["account_number"] + + @task + def apply(self): + self.user_data["email"] = self.user_data["email_id"] + self.user_data["govt_id_type"] = self.user_data["government_id_type"] + self.user_data["account_number"] = self.account_number + self.user_data["interest_rate"] = random.randint(1, 10) + self.user_data["time_period"] = random.randint(1, 10) + self.user_data["loan_amount"] = random.randint(1000, 10000) + self.user_data["loan_type"] = random.choice( + ["Base Camp", "Rover", "Potato Farming", "Ice Home", "Rocker"] + ) + self.client.post( + "/", + data=self.user_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + @task + def history(self): + self.client.post( + "/history", + data={"email": self.user_data["email_id"]}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) diff --git a/performance_tests/locust/locust.sh b/performance_tests/locust/locust.sh new file mode 100644 index 0000000..1caab94 --- /dev/null +++ b/performance_tests/locust/locust.sh @@ -0,0 +1,46 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +#!/bin/bash + +####################################################################################### +## Setup + +rm -rf venv_locust +rm -rf __pycache__ + +python3 -m venv venv_locust +source venv_locust/bin/activate +pip install -r requirements.txt + +####################################################################################### +## Running Locust + +locust -f auth_locust.py --headless -u 1 -r 1 --run-time 10s +sleep 2 + +locust -f atm_locust.py --headless -u 1 -r 1 --run-time 6s +sleep 2 + +locust -f account_locust.py --headless -u 1 -r 1 --run-time 6s +sleep 2 + +locust -f transaction_locust.py --headless -u 1 -r 1 --run-time 12s +sleep 2 + +locust -f loan_locust.py --headless -u 1 -r 1 --run-time 7s +sleep 5 + +####################################################################################### +## Cleanup + +deactivate venv_locust +rm -rf venv_locust +rm -rf __pycache__ + +####################################################################################### +## End + +echo "Done!" + diff --git a/performance_tests/locust/requirements.txt b/performance_tests/locust/requirements.txt new file mode 100644 index 0000000..3f3318c --- /dev/null +++ b/performance_tests/locust/requirements.txt @@ -0,0 +1,30 @@ +blinker==1.6.2 +Brotli==1.0.9 +certifi==2023.7.22 +charset-normalizer==3.2.0 +click==8.1.6 +ConfigArgParse==1.7 +Faker==19.2.0 +Flask==2.3.2 +Flask-BasicAuth==0.2.0 +Flask-Cors==4.0.0 +gevent==23.7.0 +geventhttpclient==2.0.9 +greenlet==2.0.2 +idna==3.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +locust==2.16.1 +MarkupSafe==2.1.3 +msgpack==1.0.5 +psutil==5.9.5 +python-dateutil==2.8.2 +pyzmq==25.1.0 +requests==2.31.0 +roundrobin==0.0.4 +six==1.16.0 +typing_extensions==4.7.1 +urllib3==2.0.4 +Werkzeug==2.3.6 +zope.event==5.0 +zope.interface==6.0 diff --git a/performance_tests/locust/transaction_locust.py b/performance_tests/locust/transaction_locust.py new file mode 100644 index 0000000..87d0f90 --- /dev/null +++ b/performance_tests/locust/transaction_locust.py @@ -0,0 +1,114 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from locust import HttpUser, task, SequentialTaskSet, between +from api_urls import ApiUrls +import random +from faker import Faker +import time +import json + +fake = Faker() + + +class MyUser(HttpUser): + host = ApiUrls["VITE_TRANSFER_URL"] + + @task + class MyUserTasks(SequentialTaskSet): + wait_time = between(2, 3) + + def on_start(self): + accounts_host = ApiUrls["VITE_ACCOUNTS_URL"] + + ##### FIRST USER ##### + + # Create fake checking account data + self.first_user = { + "name": fake.unique.name(), + "email_id": fake.unique.email(), + "account_type": "Checking", + "government_id_type": random.choice( + ["Driver's License", "Passport", "SSN"] + ), + "govt_id_number": fake.unique.ssn(), + "address": fake.unique.address(), + } + self.client.post( + f"{accounts_host}/create", + data=self.first_user, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + # Create another fake account + self.first_user["account_type"] = random.choice( + ["Savings", "Money Market", "Investment"] + ) + self.client.post( + f"{accounts_host}/create", + data=self.first_user, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + # Get all accounts + response = self.client.post( + f"{accounts_host}/allaccounts", + data={"email_id": self.first_user["email_id"]}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + self.account_numbers = [ + account["account_number"] for account in response.json()["response"] + ] + + ##### SECOND USER ##### + + self.second_user = { + "name": fake.unique.name(), + "email_id": fake.unique.email(), + "account_type": "Checking", + "government_id_type": random.choice( + ["Driver's License", "Passport", "SSN"] + ), + "govt_id_number": fake.unique.ssn(), + "address": fake.unique.address(), + } + self.client.post( + f"{accounts_host}/create", + data=self.second_user, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + @task + def internal_transfer(self): + self.client.post( + f"/", + data={ + "sender_account_number": self.account_numbers[0], + "receiver_account_number": self.account_numbers[1], + "amount": fake.random_int(min=1, max=3), + "reason": "Internal Transfer", + }, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + @task + def external_transfer(self): + self.client.post( + f"/zelle/", + data={ + "sender_email": self.first_user["email_id"], + "receiver_email": self.second_user["email_id"], + "amount": fake.random_int(min=1, max=3), + "reason": "External Transfer", + }, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + @task + def transaction_history(self): + self.client.post( + f"/history", + data={"account_number": self.account_numbers[0]}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) diff --git a/protobufs/accounts.proto b/protobufs/accounts.proto new file mode 100644 index 0000000..5f3e353 --- /dev/null +++ b/protobufs/accounts.proto @@ -0,0 +1,72 @@ +// Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +syntax = "proto3"; + +message Account { + string account_number = 1; + string email_id = 2; + string account_type = 3; + string address = 4; + string govt_id_number = 5; + string government_id_type = 6; + string name = 7; + string currency = 8; + double balance = 9; +} + + +message CreateAccountRequest { + string email_id = 1; + string account_type = 2; + string address = 3; + string govt_id_number = 4; + string government_id_type = 5; + string name = 6; +} + +message CreateAccountResponse { + bool result = 1; +} + +message GetAccountsRequest { + string email_id = 1; +} + +message GetAccountsResponse { + repeated Account accounts = 1; +} + + + + +message AccountDetail{ + string account_number = 1; + string name = 2; + double balance = 3; + string currency = 4; +} + +message GetAccountDetailRequest { + string account_number = 1; +} + + + +// message GetAccountDetailResponse { +// AccountDetail account = 1; +// } + + + + + +service AccountDetailsService { + rpc getAccountDetails(GetAccountDetailRequest) returns (AccountDetail); + rpc createAccount(CreateAccountRequest) returns (CreateAccountResponse); + rpc getAccounts(GetAccountsRequest) returns (GetAccountsResponse); +} + + + diff --git a/protobufs/loan.proto b/protobufs/loan.proto new file mode 100644 index 0000000..231b5c5 --- /dev/null +++ b/protobufs/loan.proto @@ -0,0 +1,59 @@ +// Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +syntax = "proto3"; + +message LoanRequest { + string name = 1; + string email = 2; + string account_type = 3; + string account_number = 4; + string govt_id_type = 5; + string govt_id_number = 6; + string loan_type = 7; + double loan_amount = 8; + double interest_rate = 9; + string time_period = 10; + +} + +message LoanResponse { + bool approved = 1; + string message = 2; +} + +message LoansHistoryRequest{ + string email=1; +} + + + +message Loan { + string name = 1; + string email = 2; + string account_type = 3; + string account_number = 4; + string govt_id_type = 5; + string govt_id_number = 6; + string loan_type = 7; + double loan_amount = 8; + double interest_rate = 9; + string time_period = 10; + string status = 11; + string timestamp = 12; +} + +message LoansHistoryResponse{ + repeated Loan loans = 1; +} + + + + + + +service LoanService { + rpc ProcessLoanRequest(LoanRequest) returns (LoanResponse); + rpc getLoanHistory(LoansHistoryRequest) returns (LoansHistoryResponse); +} diff --git a/protobufs/transaction.proto b/protobufs/transaction.proto new file mode 100644 index 0000000..bb2fdf8 --- /dev/null +++ b/protobufs/transaction.proto @@ -0,0 +1,58 @@ +// Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +syntax = "proto3"; + +message TransactionRequest { + string sender_account_number = 1; + string sender_account_type = 2; + string receiver_account_number = 3; + string receiver_account_type = 4; + double amount = 5; + string reason= 6; +} + +message TransactionResponse { + // string result = 1; + bool approved = 1; + // string = 2; + string message = 2; +} + +message GetALLTransactionsRequest{ + string account_number = 1; +} + +message Transaction{ + string account_number=1; + double amount = 2; + string reason=3; + string time_stamp = 4; + string type = 5; + string transaction_id = 6; +} + +message GetALLTransactionsResponse{ + repeated Transaction transactions = 1; +} + + +message ZelleRequest { + string sender_email = 1; + string receiver_email = 2; + double amount = 3; + string reason= 4; +} + +message TransactionByIDRequest{ + string transaction_id = 1; +} + + +service TransactionService { + rpc sendMoney(TransactionRequest) returns (TransactionResponse); + rpc getTransactionsHistory(GetALLTransactionsRequest) returns (GetALLTransactionsResponse); + rpc Zelle(ZelleRequest) returns (TransactionResponse); + rpc getTransactionByID(TransactionByIDRequest) returns (Transaction); +} diff --git a/scripts/run_local.sh b/scripts/run_local.sh new file mode 100644 index 0000000..5a2653c --- /dev/null +++ b/scripts/run_local.sh @@ -0,0 +1,84 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +#!/bin/bash + +### ONLY FOR MAC ### + +####################################################################################### +## Setup + +cd .. + +# Check for Dependencies +if ! command -v node &>/dev/null; then + echo "\n Node.js is not installed. Please install Node.js and npm." + exit 1 +fi + +if ! command -v python3 &>/dev/null; then + echo "\n Python 3 is not installed. Please install Python 3." + exit 1 +fi + +####################################################################################### +## Running Javascript microservices + +run_javascript_microservice() { + local service_name="$1" + local service_alias="$2" + local current_dir="$(pwd)" + + echo "Running $service_name microservice..." + + # Opening a new Terminal window + osascript -e "\ + tell application \"Terminal\" to do script \ + \"cd '$current_dir' && cd '$service_name' && \ + npm install && npm run $service_alias\"" + sleep 2 + + echo "$service_name is running ..." + echo +} + +run_javascript_microservice "ui" "ui" +run_javascript_microservice "customer-auth" "auth" +run_javascript_microservice "atm-locator" "atm" + +####################################################################################### +# Running Python microservices + +conda config --set auto_activate_base false &>/dev/null +brew update &>/dev/null +brew upgrade python3 &>/dev/null + +run_python_microservice() { + local service_name="$1" + local service_alias="$2" + local current_dir="$(pwd)" + + echo "Running $service_name microservice ..." + + # Opening a new Terminal window + osascript -e "\ + tell application \"Terminal\" to do script \ + \"cd '$current_dir' && cd '$service_name' && \ + rm -rf venv_bankapp && python3 -m venv venv_bankapp && \ + source venv_bankapp/bin/activate && \ + pip3 install -r requirements.txt && python3 '$service_alias.py'\"" + sleep 2 + + echo "$service_name is running ..." + echo +} + + +run_python_microservice "dashboard" "dashboard" +run_python_microservice "accounts" "accounts" +run_python_microservice "transactions" "transaction" +run_python_microservice "loan" "loan" + +echo "Setup completed successfully!" + diff --git a/scripts/stop_local.sh b/scripts/stop_local.sh new file mode 100644 index 0000000..59ac5bf --- /dev/null +++ b/scripts/stop_local.sh @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +####################################################################################### + +#!/bin/bash + +### ONLY FOR MAC ### + +osascript -e 'tell application "Terminal" to close every window' \ No newline at end of file diff --git a/transactions/Dockerfile b/transactions/Dockerfile new file mode 100644 index 0000000..3bee721 --- /dev/null +++ b/transactions/Dockerfile @@ -0,0 +1,18 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 python + +RUN mkdir /service +COPY protobufs/ /service/protobufs/ +COPY transactions/ /service/transactions/ +WORKDIR /service/transactions +RUN python -m pip install --upgrade pip +RUN python -m pip install -r requirements.txt + +RUN python -m grpc_tools.protoc -I ../protobufs --python_out=. --grpc_python_out=. ../protobufs/transaction.proto + +EXPOSE 50052 + +ENTRYPOINT ["python", "transaction.py"] \ No newline at end of file diff --git a/transactions/api_client.py b/transactions/api_client.py new file mode 100644 index 0000000..9233ecd --- /dev/null +++ b/transactions/api_client.py @@ -0,0 +1,16 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +import grpc + +from transaction_pb2_grpc import TransactionServiceStub +from transaction_pb2 import TransactionRequest, TransactionResponse + +channel = grpc.insecure_channel('localhost:50052') +client = TransactionServiceStub(channel) + +request = TransactionRequest(sender_account_number="3", receiver_account_number="2", amount=10.0) + +response = client.SendMoney (request) +print(response) \ No newline at end of file diff --git a/transactions/requirements.txt b/transactions/requirements.txt new file mode 100644 index 0000000..31f0165 --- /dev/null +++ b/transactions/requirements.txt @@ -0,0 +1,23 @@ +attrs==23.1.0 +click==7.1.2 +dnspython==2.3.0 +Flask==1.1.4 +Flask-Cors==3.0.10 +grpcio==1.54.2 +grpcio-tools==1.54.2 +itsdangerous==1.1.0 +Jinja2==2.11.3 +MarkupSafe==2.0.1 +more-itertools==9.1.0 +packaging==23.1 +pluggy==0.13.1 +protobuf==4.23.2 +py==1.11.0 +pymongo==4.3.3 +pytest==5.4.3 +six==1.16.0 +wcwidth==0.2.6 +Werkzeug==1.0.1 +requests +dotmap +python-dotenv==1.0.0 \ No newline at end of file diff --git a/transactions/transaction.py b/transactions/transaction.py new file mode 100644 index 0000000..ae0f8c2 --- /dev/null +++ b/transactions/transaction.py @@ -0,0 +1,345 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +from concurrent import futures +import datetime +from bson.objectid import ObjectId +import os +import grpc +from flask import Flask, request, jsonify + +from dotmap import DotMap + +# Configure the logging settings +import logging + +logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s" +) +from transaction_pb2 import * +import transaction_pb2_grpc +from flask import Flask, request, jsonify + +from google.protobuf.json_format import MessageToDict + +from dotenv import load_dotenv +load_dotenv() + +import logging + +# set logging to debug +logging.basicConfig(level=logging.DEBUG) + +from pymongo.mongo_client import MongoClient + + +# db_host = os.getenv("DATABASE_HOST", "localhost") + +db_url = os.getenv("DB_URL") +if db_url is None: + raise Exception("DB_URL environment variable is not set") + +uri = db_url + + +# protocol = os.getenv('SERVICE_PROTOCOL') +protocol = os.getenv('SERVICE_PROTOCOL', 'http') +if protocol is None: + raise Exception("SERVICE_PROTOCOL environment variable is not set") + +protocol = protocol.lower() +logging.debug(f"microservice protocol: {protocol}") + + + +client = MongoClient(uri) +db = client["bank"] +collection_accounts = db["accounts"] +collection_transactions = db["transactions"] + + +class TransactionGeneric: + def SendMoney(self, request): + sender_account = self.__getAccount(request.sender_account_number) + receiver_account = self.__getAccount(request.receiver_account_number) + return self.__transfer( + sender_account, receiver_account, float(request.amount), request.reason + ) + + def GetTransactionByID(self, request): + transaction_id = request.transaction_id + logging.debug(f"Transaction ID: {transaction_id}") + count = collection_transactions.count_documents( + {"_id": ObjectId(transaction_id)} + ) + + if count == 0: + return {} + + transaction = collection_transactions.find_one( + {"_id": ObjectId(transaction_id)} + ) + + return { + "account_number": transaction["receiver"], + "amount": transaction["amount"], + "reason": transaction["reason"], + "time_stamp": f"{transaction['time_stamp']}", + "type": "credit", + "transaction_id": str(transaction["_id"]), + } + + def GetTransactionsHistory(self, request): + account_number = request.account_number + # logging.debug(f"Account Number: {account_number}") + + # find based on account number only based on sender + transactions_credit = collection_transactions.find({"sender": account_number}) + transactions_debit = collection_transactions.find({"receiver": account_number}) + + transactions_list = [] + for t in transactions_credit: + temp_t = { + "account_number": t["receiver"], + "amount": t["amount"], + "reason": t["reason"], + "time_stamp": f"{t['time_stamp']}", + "type": "credit", + "transaction_id": str(t["_id"]), + } + transactions_list.append(temp_t) + + for t in transactions_debit: + temp_t = { + "account_number": t["receiver"], + "amount": t["amount"], + "reason": t["reason"], + "time_stamp": f"{t['time_stamp']}", + "type": "credit", + "transaction_id": str(t["_id"]), + } + transactions_list.append(temp_t) + + return transactions_list + + def Zelle(self, request): + sender_email = request.sender_email + receiver_email = request.receiver_email + amount = float(request.amount) + reason = request.reason + sender_account = self.__getAccountwithEmail(sender_email) + receiver_account = self.__getAccountwithEmail(receiver_email) + + return self.__transfer(sender_account, receiver_account, amount, reason) + + def __transfer(self, sender_account, receiver_account, amount, reason): + # if sender_account is not None or receiver_account is not None: + + if sender_account is None: + return {"approved": False, "message": "Sender Account Not Found."} + + if receiver_account is None: + return {"approved": False, "message": "Receiver Account Not Found."} + + + result = self.__doTransaction( + sender_account, receiver_account, amount, reason=reason + ) + logging.debug(f"---> sender: {sender_account}") + logging.debug(f"--->receiver: {receiver_account}") + return result + + def __doTransaction(self, sender, receiver, amount, reason=""): + if sender["balance"] < amount: + return {"approved": False, "message": "Insufficient Balance"} + + sender["balance"] -= amount + receiver["balance"] += amount + + # update sender account + collection_accounts.update_one( + {"account_number": sender["account_number"]}, + {"$set": {"balance": sender["balance"]}}, + ) + + # update receiver account + collection_accounts.update_one( + {"account_number": receiver["account_number"]}, + {"$set": {"balance": receiver["balance"]}}, + ) + + # add transaction + collection_transactions.insert_one( + { + "sender": sender["account_number"], + "receiver": receiver["account_number"], + "amount": amount, + "reason": reason, + "time_stamp": datetime.datetime.now(), + } + ) + + return {"approved": True, "message": "Transaction is Successful."} + + def __getAccountwithEmail(self, email): + logging.debug(f"Email: {email}") + # log the document with the email + logging.debug( + f"Document with email: {collection_accounts.count_documents({'email_id': email, 'account_type': 'Checking'})}" + ) + + document = None + + if ( + collection_accounts.count_documents( + {"email_id": email, "account_type": "Checking"} + ) + == 1 + ): + checking_account = collection_accounts.find( + {"email_id": email, "account_type": "Checking"} + ) + document = checking_account[0] + logging.debug(f"Checking Account: {document}") + return document + else: + if ( + collection_accounts.count_documents( + {"email_id": email, "account_type": "Savings"} + ) + == 1 + ): + saving_account = collection_accounts.find( + {"email_id": email, "account_type": "Savings"} + ) + document = saving_account[0] + logging.debug(f"Savings Account: {document}") + return document + # logging.debug(f"Savings Account: {document}") + logging.debug("No Account Found") + return document + + def __getAccount(self, account_num): + r = None + accounts = collection_accounts.find() + # logging.debug(f"Accounts: {list(accounts)}") + for acc in accounts: + if acc["account_number"] == account_num: + r = acc + break + # logging.debug(f"Account {r}") + return r + + +class TransactionService(transaction_pb2_grpc.TransactionServiceServicer): + def __init__(self): + self.transaction = TransactionGeneric() + + def sendMoney(self, request, context): + t = TransactionResponse() + result = self.transaction.SendMoney(request) + t.approved = result["approved"] + t.message = result["message"] + return t + + def Zelle(self, request, context): + result = self.transaction.Zelle(request) + t = TransactionResponse(approved=result["approved"], message=result["message"]) + return t + + def getTransactionByID(self, request, context): + result = self.transaction.GetTransactionByID(request) + if len(result) == 0: + return Transaction() + else: + return Transaction( + account_number=result["account_number"], + amount=result["amount"], + reason=result["reason"], + time_stamp=result["time_stamp"], + type="credit", + transaction_id=result["transaction_id"], + ) + + def getTransactionsHistory(self, request, context): + results = self.transaction.GetTransactionsHistory(request) + transactions_list = [] + for t in results: + temp_t = Transaction( + account_number=t["account_number"], + amount=t["amount"], + reason=t["reason"], + time_stamp=t["time_stamp"], + type=t["type"], + transaction_id=t["transaction_id"], + ) + transactions_list.append(temp_t) + + return GetALLTransactionsResponse(transactions=transactions_list) + + + + + + +app = Flask(__name__) +transaction_generic = TransactionGeneric() + +@app.route("/transfer", methods=["POST"]) +def sendMoney(): + data = request.json + data = DotMap(data) + result = transaction_generic.SendMoney(data) + return jsonify(result) + +@app.route("/zelle", methods=["POST"]) +def zelle(): + logging.debug(" Zelle API called") + data = request.json + data = DotMap(data) + result = transaction_generic.Zelle(data) + return jsonify(result) + +@app.route("/transaction-with-id", methods=["POST"]) +def getTransactionByID(): + logging.debug(" Get Transaction By ID API called") + data = request.json + data = DotMap(data) + result = transaction_generic.GetTransactionByID(data) + return jsonify(result) + +@app.route("/transaction-history", methods=["POST"]) +def getTransactionsHistory(): + data = request.json + data = DotMap(data) + result = transaction_generic.GetTransactionsHistory(data) + return jsonify(result) + + + +def serverFlask(port): + logging.debug(f"Starting Flask server on port {port}") + app.run(host='0.0.0.0' ,port=port, debug=True) + + +def serverGRPC(port): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + transaction_pb2_grpc.add_TransactionServiceServicer_to_server( + TransactionService(), server + ) + server.add_insecure_port(f"[::]:{port}") + logging.debug(f"Starting server. Listening on port {port}.") + server.start() + server.wait_for_termination() + +if __name__ == "__main__": + port = 50052 + # serverGRPC(port) + # serverFlask(port) + + if protocol == "grpc": + serverGRPC(port) + else: + serverFlask(port) + diff --git a/transactions/transaction_pb2.py b/transactions/transaction_pb2.py new file mode 100644 index 0000000..1696859 --- /dev/null +++ b/transactions/transaction_pb2.py @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: transaction.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11transaction.proto\"\xb0\x01\n\x12TransactionRequest\x12\x1d\n\x15sender_account_number\x18\x01 \x01(\t\x12\x1b\n\x13sender_account_type\x18\x02 \x01(\t\x12\x1f\n\x17receiver_account_number\x18\x03 \x01(\t\x12\x1d\n\x15receiver_account_type\x18\x04 \x01(\t\x12\x0e\n\x06\x61mount\x18\x05 \x01(\x01\x12\x0e\n\x06reason\x18\x06 \x01(\t\"8\n\x13TransactionResponse\x12\x10\n\x08\x61pproved\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"3\n\x19GetALLTransactionsRequest\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\"\x7f\n\x0bTransaction\x12\x16\n\x0e\x61\x63\x63ount_number\x18\x01 \x01(\t\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x01\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\ntime_stamp\x18\x04 \x01(\t\x12\x0c\n\x04type\x18\x05 \x01(\t\x12\x16\n\x0etransaction_id\x18\x06 \x01(\t\"@\n\x1aGetALLTransactionsResponse\x12\"\n\x0ctransactions\x18\x01 \x03(\x0b\x32\x0c.Transaction\"\\\n\x0cZelleRequest\x12\x14\n\x0csender_email\x18\x01 \x01(\t\x12\x16\n\x0ereceiver_email\x18\x02 \x01(\t\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x01\x12\x0e\n\x06reason\x18\x04 \x01(\t\"0\n\x16TransactionByIDRequest\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t2\x8a\x02\n\x12TransactionService\x12\x36\n\tsendMoney\x12\x13.TransactionRequest\x1a\x14.TransactionResponse\x12Q\n\x16getTransactionsHistory\x12\x1a.GetALLTransactionsRequest\x1a\x1b.GetALLTransactionsResponse\x12,\n\x05Zelle\x12\r.ZelleRequest\x1a\x14.TransactionResponse\x12;\n\x12getTransactionByID\x12\x17.TransactionByIDRequest\x1a\x0c.Transactionb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'transaction_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _TRANSACTIONREQUEST._serialized_start=22 + _TRANSACTIONREQUEST._serialized_end=198 + _TRANSACTIONRESPONSE._serialized_start=200 + _TRANSACTIONRESPONSE._serialized_end=256 + _GETALLTRANSACTIONSREQUEST._serialized_start=258 + _GETALLTRANSACTIONSREQUEST._serialized_end=309 + _TRANSACTION._serialized_start=311 + _TRANSACTION._serialized_end=438 + _GETALLTRANSACTIONSRESPONSE._serialized_start=440 + _GETALLTRANSACTIONSRESPONSE._serialized_end=504 + _ZELLEREQUEST._serialized_start=506 + _ZELLEREQUEST._serialized_end=598 + _TRANSACTIONBYIDREQUEST._serialized_start=600 + _TRANSACTIONBYIDREQUEST._serialized_end=648 + _TRANSACTIONSERVICE._serialized_start=651 + _TRANSACTIONSERVICE._serialized_end=917 +# @@protoc_insertion_point(module_scope) diff --git a/transactions/transaction_pb2_grpc.py b/transactions/transaction_pb2_grpc.py new file mode 100644 index 0000000..0fedc4d --- /dev/null +++ b/transactions/transaction_pb2_grpc.py @@ -0,0 +1,169 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import transaction_pb2 as transaction__pb2 + + +class TransactionServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.sendMoney = channel.unary_unary( + '/TransactionService/sendMoney', + request_serializer=transaction__pb2.TransactionRequest.SerializeToString, + response_deserializer=transaction__pb2.TransactionResponse.FromString, + ) + self.getTransactionsHistory = channel.unary_unary( + '/TransactionService/getTransactionsHistory', + request_serializer=transaction__pb2.GetALLTransactionsRequest.SerializeToString, + response_deserializer=transaction__pb2.GetALLTransactionsResponse.FromString, + ) + self.Zelle = channel.unary_unary( + '/TransactionService/Zelle', + request_serializer=transaction__pb2.ZelleRequest.SerializeToString, + response_deserializer=transaction__pb2.TransactionResponse.FromString, + ) + self.getTransactionByID = channel.unary_unary( + '/TransactionService/getTransactionByID', + request_serializer=transaction__pb2.TransactionByIDRequest.SerializeToString, + response_deserializer=transaction__pb2.Transaction.FromString, + ) + + +class TransactionServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def sendMoney(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getTransactionsHistory(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Zelle(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def getTransactionByID(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TransactionServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'sendMoney': grpc.unary_unary_rpc_method_handler( + servicer.sendMoney, + request_deserializer=transaction__pb2.TransactionRequest.FromString, + response_serializer=transaction__pb2.TransactionResponse.SerializeToString, + ), + 'getTransactionsHistory': grpc.unary_unary_rpc_method_handler( + servicer.getTransactionsHistory, + request_deserializer=transaction__pb2.GetALLTransactionsRequest.FromString, + response_serializer=transaction__pb2.GetALLTransactionsResponse.SerializeToString, + ), + 'Zelle': grpc.unary_unary_rpc_method_handler( + servicer.Zelle, + request_deserializer=transaction__pb2.ZelleRequest.FromString, + response_serializer=transaction__pb2.TransactionResponse.SerializeToString, + ), + 'getTransactionByID': grpc.unary_unary_rpc_method_handler( + servicer.getTransactionByID, + request_deserializer=transaction__pb2.TransactionByIDRequest.FromString, + response_serializer=transaction__pb2.Transaction.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'TransactionService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class TransactionService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def sendMoney(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/sendMoney', + transaction__pb2.TransactionRequest.SerializeToString, + transaction__pb2.TransactionResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getTransactionsHistory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/getTransactionsHistory', + transaction__pb2.GetALLTransactionsRequest.SerializeToString, + transaction__pb2.GetALLTransactionsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Zelle(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/Zelle', + transaction__pb2.ZelleRequest.SerializeToString, + transaction__pb2.TransactionResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def getTransactionByID(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/TransactionService/getTransactionByID', + transaction__pb2.TransactionByIDRequest.SerializeToString, + transaction__pb2.Transaction.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/ui/.dockerignore b/ui/.dockerignore new file mode 100644 index 0000000..1781d93 --- /dev/null +++ b/ui/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +Dockerfile \ No newline at end of file diff --git a/ui/.eslintrc.cjs b/ui/.eslintrc.cjs new file mode 100644 index 0000000..baec2d8 --- /dev/null +++ b/ui/.eslintrc.cjs @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +module.exports = { + env: { browser: true, es2020: true, node: true }, + // env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + ], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': 'warn', + "react/prop-types": "off", + }, +} diff --git a/ui/Dockerfile b/ui/Dockerfile new file mode 100644 index 0000000..0899c89 --- /dev/null +++ b/ui/Dockerfile @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM --platform=linux/amd64 node:18-alpine +WORKDIR /bankapp +EXPOSE 3000 +COPY package.json package-lock.json ./ +RUN npm install --silent +COPY . ./ +CMD ["npm", "run", "ui"] \ No newline at end of file diff --git a/ui/LICENSE b/ui/LICENSE new file mode 100644 index 0000000..2bdf814 --- /dev/null +++ b/ui/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2022 Cisco Systems, Inc. and its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/ui/babel-plugin-macros.config.js b/ui/babel-plugin-macros.config.js new file mode 100644 index 0000000..42e7c87 --- /dev/null +++ b/ui/babel-plugin-macros.config.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +module.exports = { + "fontawesome-svg-core": { + license: "free", + }, +}; diff --git a/ui/babel.config.js b/ui/babel.config.js new file mode 100644 index 0000000..dc1c1a3 --- /dev/null +++ b/ui/babel.config.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +module.exports = function (api) { + return { + plugins: ["macros"], + }; +}; diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..81b0d30 --- /dev/null +++ b/ui/index.html @@ -0,0 +1,47 @@ + + + + + + + + + + Martian Bank + + + +
+ + + diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 0000000..7678309 --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,4407 @@ +{ + "name": "ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "0.0.0", + "dependencies": { + "@fortawesome/fontawesome-free": "^6.4.0", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "@reduxjs/toolkit": "^1.9.5", + "axios": "^1.4.0", + "babel-plugin-macros": "^3.1.0", + "bootstrap": "^5.2.3", + "google-map-react": "^2.2.1", + "js-cookie": "^3.0.5", + "leaflet": "^1.9.4", + "mdb-react-ui-kit": "^6.1.0", + "react": "^18.2.0", + "react-bootstrap": "^2.7.4", + "react-dom": "^18.2.0", + "react-icons": "^4.8.0", + "react-leaflet": "^4.2.1", + "react-query": "^3.39.3", + "react-redux": "^8.0.5", + "react-router-bootstrap": "^0.26.2", + "react-router-dom": "^6.11.0", + "react-toastify": "^9.1.2", + "save": "^2.9.0" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^4.0.0", + "eslint": "^8.38.0", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "vite": "^4.3.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", + "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", + "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", + "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", + "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz", + "integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", + "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@googlemaps/js-api-loader": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.2.tgz", + "integrity": "sha512-psGw5u0QM6humao48Hn4lrChOM2/rA43ZCm3tKK9qQsEj1/VzqkCqnvGfEOshDbBQflydfaRovbKwZMF4AyqbA==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.6.0.tgz", + "integrity": "sha512-OFiYQdv+Yk7AO7IsQu/fAEPijbeTwrrEYvdNoJ3sblBBedD5j5fBTNWrUPNVlwC4XWWnWTCMaRIVsJujsFiWXg==", + "dependencies": { + "@swc/helpers": "^0.4.14" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz", + "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==", + "dependencies": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz", + "integrity": "sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.9.tgz", + "integrity": "sha512-3BekqcwB6Umeya+16XPooARn4qEPW6vNvwYnlofIYe6h9qG1/VeD7UvShCWx11eFz5ELYmwIEshz+MkPX3wjcQ==", + "dependencies": { + "dequal": "^2.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.6.tgz", + "integrity": "sha512-eC3puKuWE1SRYbojWHXnvCNHGgf3uzHCb6JOhnF4OXPibOIPEkR1sqDSkL643ydigxwh+ruCa1CmYHlzk7ikKA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.2.tgz", + "integrity": "sha512-/GDx+K1STGtpgTsj5Dj3J51YaKxZDblbCQHTH1zHLuoBEWodj6MjtRVv3TUijj1JYLRLSFsFzN8NV4M3QV4d9w==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.11.tgz", + "integrity": "sha512-+hsJr9hmwyDecSMQAmX7drgbDpyE+EgSF6t7+5QEBAn1tQK7kl1vWZ4iRf6SjQ8lk7dyEULxUmZOIpN0W5baZA==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", + "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, + "node_modules/@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA==" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz", + "integrity": "sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.4", + "@babel/plugin-transform-react-jsx-self": "^7.21.0", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bootstrap": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0.tgz", + "integrity": "sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.7" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, + "node_modules/browserslist": { + "version": "4.21.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", + "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001489", + "electron-to-chromium": "^1.4.411", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001502", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001502.tgz", + "integrity": "sha512-AZ+9tFXw1sS0o0jcpJQIXvFTOB/xGiQ4OQ2t98QX3NDn2EZTSRBC801gxrsGgViuq2ak/NLkNgSNEPtCr5lfKg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.427", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.427.tgz", + "integrity": "sha512-HK3r9l+Jm8dYAm1ctXEWIC+hV60zfcjS9UA5BDlYvnI5S7PU/yytjpvSrTNrSSRRkuu3tDyZhdkwIczh+0DWaw==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", + "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.3.5.tgz", + "integrity": "sha512-61qNIsc7fo9Pp/mju0J83kzvLm0Bsayu7OQSLEoJxLDCBjIIyb87bkzufoOvdDxLkSlMfkF7UxomC4+eztUBSA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "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" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", + "dependencies": { + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==" + }, + "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==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-map-react": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/google-map-react/-/google-map-react-2.2.1.tgz", + "integrity": "sha512-Dg8aexf5rNSmywj0XKQ5m4RNzVcWwKEM2BGDj5aPChD0um8ZRjB5Upcb/yg/i0oG1aES29asQ5+6BHVgrK5xGA==", + "dependencies": { + "@googlemaps/js-api-loader": "^1.13.8", + "@mapbox/point-geometry": "^0.1.0", + "eventemitter3": "^4.0.4", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==" + }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, + "node_modules/mdb-react-ui-kit": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/mdb-react-ui-kit/-/mdb-react-ui-kit-6.1.0.tgz", + "integrity": "sha512-IvtD0abO9St+nbGT2xBvKuFm1ZotrdoVGJ/QgXSboWgYhiJYvR9kcpwzgUIlLGmBnuo/JZOyXcwhuPAbF1BDQQ==", + "dependencies": { + "@popperjs/core": "2.11.5", + "clsx": "1.1.1", + "react-popper": "2.3.0" + }, + "peerDependencies": { + "@types/react": "^18.0.9", + "@types/react-dom": "^18.0.3", + "react": "^18.1.0", + "react-dom": "^18.1.0" + } + }, + "node_modules/mdb-react-ui-kit/node_modules/@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/mdb-react-ui-kit/node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mingo": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/mingo/-/mingo-6.4.3.tgz", + "integrity": "sha512-sR4uAFRXwo634Jm8xaz0V1t4D0hzJrrkD8BIoDobLt2mrwweN+PlLDeZ10JrdC4OgBRB1z26Vb5qvHB24JnkZQ==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.7.4.tgz", + "integrity": "sha512-EPKPwhfbxsKsNBhJBitJwqul9fvmlYWSft6jWE2EpqhEyjhqIqNihvQo2onE5XtS+QHOavUSNmA+8Lnv5YeAyg==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.3", + "@types/react-transition-group": "^4.4.5", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-icons": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz", + "integrity": "sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "dependencies": { + "@react-leaflet/core": "^2.1.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.7.tgz", + "integrity": "sha512-1vRQuCQI5Y2uNmrMXg81RXKiBHY3jBzvCvNmZF437O/Z9/pZ+ba2uYHbemYXb3g8rjsacBGo+/wmfrQKzMhJsg==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@reduxjs/toolkit": "^1 || ^2.0.0-beta.0", + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@reduxjs/toolkit": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.12.1.tgz", + "integrity": "sha512-evd/GrKJOeOypD0JB9e1r7pQh2gWCsTbUfq059Wm1AFT/K2MNZuDo19lFtAgIhlBrp0MmpgpqtvZC7LPAs7vSw==", + "dependencies": { + "@remix-run/router": "1.6.3" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-bootstrap": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/react-router-bootstrap/-/react-router-bootstrap-0.26.2.tgz", + "integrity": "sha512-YlpI9Xi+Uqp6zFAUO8D/wu6P8mr1ujqq+0V5MhJG1kx9dr/95fAMoGk4J+/CsysOkwtR3tYSac4DDWmHwXvC8w==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">=16.13.1", + "react-router-dom": ">=6.0.0" + } + }, + "node_modules/react-router-dom": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.12.1.tgz", + "integrity": "sha512-POIZN9UDKWwEDga054LvYr2KnK8V+0HR4Ny4Bwv8V7/FZCPxJgsCjYxXGxqxzHs7VBxMKZfgvtKhafuJkJSPGA==", + "dependencies": { + "@remix-run/router": "1.6.3", + "react-router": "6.12.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, + "node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", + "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/save": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/save/-/save-2.9.0.tgz", + "integrity": "sha512-eg8+g8CjvehE/2C6EbLdtK1pINVD27pcJLj4M9PjWWhoeha/y5bWf4dp/0RF+OzbKTcG1bae9qi3PAqiR8CJTg==", + "dependencies": { + "async": "^3.2.2", + "event-stream": "^4.0.1", + "lodash.assign": "^4.2.0", + "mingo": "^6.1.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/vite": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..b7deb2d --- /dev/null +++ b/ui/package.json @@ -0,0 +1,49 @@ +{ + "name": "ui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "ui": "vite", + "build": "npm run build", + "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0 --fix", + "preview": "vite preview", + "client": "npm run dev" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.4.0", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "@reduxjs/toolkit": "^1.9.5", + "axios": "^1.4.0", + "babel-plugin-macros": "^3.1.0", + "bootstrap": "^5.2.3", + "google-map-react": "^2.2.1", + "js-cookie": "^3.0.5", + "leaflet": "^1.9.4", + "mdb-react-ui-kit": "^6.1.0", + "react": "^18.2.0", + "react-bootstrap": "^2.7.4", + "react-dom": "^18.2.0", + "react-icons": "^4.8.0", + "react-leaflet": "^4.2.1", + "react-query": "^3.39.3", + "react-redux": "^8.0.5", + "react-router-bootstrap": "^0.26.2", + "react-router-dom": "^6.11.0", + "react-toastify": "^9.1.2", + "save": "^2.9.0" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@vitejs/plugin-react": "^4.0.0", + "eslint": "^8.38.0", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "vite": "^4.3.2" + } +} diff --git a/ui/public/vite.svg b/ui/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/ui/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/App.jsx b/ui/src/App.jsx new file mode 100644 index 0000000..3d9fbea --- /dev/null +++ b/ui/src/App.jsx @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { Container } from "react-bootstrap"; +import { Outlet } from "react-router-dom"; +import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import Header from "./components/Header"; + +const App = () => { + return ( +
+
+ + + + +
+ ); +}; + +export default App; diff --git a/ui/src/components/AccountDisclosure.jsx b/ui/src/components/AccountDisclosure.jsx new file mode 100644 index 0000000..f409641 --- /dev/null +++ b/ui/src/components/AccountDisclosure.jsx @@ -0,0 +1,72 @@ +import Card from "react-bootstrap/Card"; + +const AccountDisclosure = () => { + return ( + + + + + + Account Disclosures + +
+
+ Investment and Insurance Products are: +
+
    +
  • + Not Insured by the MFIC (Martian Financial Institutions + Commission) or Any Martian Government Agency +
  • +
  • + Not a Deposit or Other Obligation of, or Guaranteed by, the Bank + or Any Bank Affiliate +
  • +
  • + Subject to Investment Risks, Including Possible Loss of the + Principal Amount Invested +
  • +
+ + Investment products and services are offered through Martian Bank + Advisors. Martian Bank Advisors is a trade name used by Martian + Clearing Services, LLC (MCSC) and Martian Bank Advisors Financial + Network, LLC, Members MPIC (Martian Planetary Investment + Commission), separate registered broker-dealers and non-bank + affiliates of Martian Bank Corporation. + +
+ + Deposit products offered by Martian Bank, M.A. Member MFDIC + (Martian Financial Deposit Insurance Corporation). + +
+ Equal Planetary Habitat Lender +
+ + At Martian Bank, we are committed to promoting sustainability and + supporting environmental initiatives across our operations. Join + us in our mission to build a greener and more sustainable future + for all Martian inhabitants. + +
+ + For inquiries regarding our products and services or to learn more + about our commitment to environmental sustainability, please visit + our website or contact our customer support team. + +
+
+ © 2023 Martian Bank. All rights reserved. +
+
+
+
+ ); +}; + +export default AccountDisclosure; diff --git a/ui/src/components/AccountTnC.jsx b/ui/src/components/AccountTnC.jsx new file mode 100644 index 0000000..ac7704f --- /dev/null +++ b/ui/src/components/AccountTnC.jsx @@ -0,0 +1,51 @@ +import { Modal } from "react-bootstrap"; + +const TermsAndConditionsModal = () => { + return ( + <> + + Terms and Conditions + + +

+ Welcome to Martian Bank! By opening an account with us, you agree to + the following terms and conditions: +

+

1. Eligibility

+

+ To open an account with Martian Bank, you must be a resident of Mars + and at least 18 years old. You may be required to provide proof of + identity and other supporting documents. +

+

2. Account Information

+

+ You are responsible for providing accurate and up-to-date information + during the account opening process. It is essential to keep your + account information confidential and not share it with others. +

+

3. Fees and Charges

+

+ Martian Bank may impose fees and charges for certain account services. + These fees will be disclosed to you during the account opening process + and may be subject to change. It is your responsibility to review and + understand the applicable fees. +

+

4. Termination

+

+ Martian Bank reserves the right to terminate or suspend your account + if you violate the terms and conditions or engage in fraudulent or + illegal activities. You may also request to close your account at any + time, subject to any outstanding obligations. +

+

+ By opening an account with Martian Bank, you acknowledge that you have + read, understood, and agreed to these Terms and Conditions. If you + have any questions or concerns, please contact our customer support + team. +

+
+ + ); +}; + +export default TermsAndConditionsModal; diff --git a/ui/src/components/Footer.jsx b/ui/src/components/Footer.jsx new file mode 100644 index 0000000..244fcc6 --- /dev/null +++ b/ui/src/components/Footer.jsx @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { Row, Col, Card } from "react-bootstrap"; +import "../index.css"; + +const Footer = () => { + return ( +
+ + + + + + + + Account Disclosures + +
+
+ Investment and Insurance Products are: + 1. Not Insured by the MFIC (Martian Financial Institutions + Commission) or Any Martian Government Agency 2. Not a Deposit + or Other Obligation of, or Guaranteed by, the Bank or 3. Any + Bank Affiliate Subject to Investment Risks, Including Possible + Loss of the Principal Amount Invested + + Investment products and services are offered through Martian + Bank Advisors. Martian Bank Advisors is a trade name used by + Martian Clearing Services, LLC (MCSC) and Martian Bank + Advisors Financial Network, LLC, Members MPIC (Martian + Planetary Investment Commission), separate registered + broker-dealers and non-bank affiliates of Martian Bank + Corporation. + +
+ + Deposit products offered by Martian Bank, M.A. Member MFDIC + (Martian Financial Deposit Insurance Corporation). + +
+ Equal Planetary Habitat Lender +
+ + MFICO is a registered trademark of Martian Isaac Corporation + in Mars and other celestial bodies. + +
+
+
+
+ +
+
+ ); +}; + +export default Footer; diff --git a/ui/src/components/FormContainer.jsx b/ui/src/components/FormContainer.jsx new file mode 100644 index 0000000..d357009 --- /dev/null +++ b/ui/src/components/FormContainer.jsx @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { Container, Row, Col } from "react-bootstrap"; + +const FormContainer = ({ children, position }) => { + return ( + + {position === "left" ? ( + <> + + + {children} + + + + + +

$100 bonus on us!

+

+ Open an eligible account with qualifying electronic deposits and get $100 bonus. +

+ +
+ + + card + + + +
+ + ) : ( + + + {children} + + + )} +
+ ); +}; + +export default FormContainer; diff --git a/ui/src/components/Header.jsx b/ui/src/components/Header.jsx new file mode 100644 index 0000000..93cfb4e --- /dev/null +++ b/ui/src/components/Header.jsx @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { Navbar, Nav, Container, NavDropdown } from "react-bootstrap"; +import { toast } from "react-toastify"; +import { LinkContainer } from "react-router-bootstrap"; +import { useSelector, useDispatch } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { useLogoutMutation } from "../slices/usersApiSlice"; +import { logout } from "../slices/authSlice"; +import "../index.css"; + +const CustomNavItems = ({ name, link }) => { + return ( + + + + {name} + + + + ); +}; + +const Header = () => { + const { userInfo } = useSelector((state) => state.auth); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const [logoutApiCall] = useLogoutMutation(); + + const logoutHandler = async () => { + try { + // const jwtCookie = Cookies.get("jwt"); + // if (!jwtCookie) { + // toast.error("No JWT cookie found!"); + // } else { + // await logoutApiCall(jwtCookie).unwrap(); + // dispatch(logout()); + // toast.success("Logged out", { + // className: "toast-container-custom", + // autoClose: false, + // hideProgressBar: true, + // closeOnClick: true, + // pauseOnHover: true, + // draggable: true, + // progress: undefined, + // theme: "dark", + // }); + await logoutApiCall({email: userInfo.email}).unwrap(); + dispatch(logout()); + toast.success("Logged out", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + navigate("/login"); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + + + + logo + Martian + + Bank + + + + + + + + + +
+ ); +}; + +export default Header; diff --git a/ui/src/components/Hero.jsx b/ui/src/components/Hero.jsx new file mode 100644 index 0000000..54dda89 --- /dev/null +++ b/ui/src/components/Hero.jsx @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { Container, Card, Button, Col, Row } from "react-bootstrap"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { LinkContainer } from "react-router-bootstrap"; +import { + faMoneyBillWave, + faShieldAlt, + faRocket, +} from "@fortawesome/free-solid-svg-icons"; + +const Hero = () => { + return ( +
+ + +

+ + Welcome to Martian Bank + +

+

+ Secure your Martian finances with Red Planet Bank - your trusted + financial partner on the Red Planet. Explore our innovative banking + solutions, enjoy top-notch security measures, and fuel your Martian + ventures with our competitive loans and investment opportunities. +

+
+ + + + + + +
+
+ + + + + + Flexible Banking Solutions + + Enjoy a wide range of banking products and services + tailored to your Martian needs. + + + + + + + + + Top-Notch Security + + Rest easy knowing that your Martian assets are protected + with us. + + + + + + + + + Martian Ventures + + Fuel your Martian dreams with our competitive loans and + investment opportunities. + + + + + +
+
+
+
+ ); +}; + +export default Hero; diff --git a/ui/src/components/Loader.jsx b/ui/src/components/Loader.jsx new file mode 100644 index 0000000..438e410 --- /dev/null +++ b/ui/src/components/Loader.jsx @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { Spinner } from "react-bootstrap"; + +const Loader = () => { + return ( + + ); +}; + +export default Loader; diff --git a/ui/src/components/PrivateRoute.jsx b/ui/src/components/PrivateRoute.jsx new file mode 100644 index 0000000..9ddfb6b --- /dev/null +++ b/ui/src/components/PrivateRoute.jsx @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { Navigate, Outlet } from "react-router-dom"; +import { useSelector } from "react-redux"; + +const PrivateRoute = () => { + const { userInfo } = useSelector((state) => state.auth); + return userInfo ? : ; +}; +export default PrivateRoute; diff --git a/ui/src/index.css b/ui/src/index.css new file mode 100644 index 0000000..7d86276 --- /dev/null +++ b/ui/src/index.css @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +.custom-nav-dropdown .nav-link{ + color: white !important; +} + +.leaflet-container { + height: 57vh; + width: 40vw; +} + +body { + font-family: Roboto, Helvetica, Arial, sans-serif; +} + +.toast-container-custom { + top: 10vh !important; + margin: 0; + background-color: #212529; +} + + \ No newline at end of file diff --git a/ui/src/main.jsx b/ui/src/main.jsx new file mode 100644 index 0000000..78c4ebd --- /dev/null +++ b/ui/src/main.jsx @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import React from "react"; +import ReactDOM from "react-dom/client"; +import 'mdb-react-ui-kit/dist/css/mdb.min.css'; +import "@fortawesome/fontawesome-free/css/all.min.css"; +import App from "./App.jsx"; +import { + createBrowserRouter, + createRoutesFromElements, + Route, + RouterProvider, +} from "react-router-dom"; +import "./index.css"; +import "bootstrap/dist/css/bootstrap.min.css"; +import store from "./store"; +import { Provider } from "react-redux"; +import HomeScreen from "./screens/HomeScreen"; +import LoginScreen from "./screens/LoginScreen.jsx"; +import RegisterScreen from "./screens/RegisterScreen.jsx"; +import ProfileScreen from "./screens/ProfileScreen.jsx"; +import PrivateRoute from "./components/PrivateRoute.jsx"; +import AtmScreen from "./screens/AtmScreen.jsx"; +import NewAccScreen from "./screens/NewAccScreen.jsx"; +import AccInfoScreen from "./screens/AccInfoScreen.jsx"; +import TransferScreen from "./screens/TransferScreen.jsx"; +import TransactionScreen from "./screens/TransactionScreen.jsx"; +import LoanScreen from "./screens/LoanScreen.jsx"; +import ApplyLoan from "./screens/ApplyLoanScreen.jsx"; + +const router = createBrowserRouter( + createRoutesFromElements( + }> + } /> + } /> + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ) +); + +ReactDOM.createRoot(document.getElementById("root")).render( + + + + + +); diff --git a/ui/src/screens/AccInfoScreen.jsx b/ui/src/screens/AccInfoScreen.jsx new file mode 100644 index 0000000..0298d16 --- /dev/null +++ b/ui/src/screens/AccInfoScreen.jsx @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState } from "react"; +import { Link } from "react-router-dom"; +import { Form, Button, Row, Col } from "react-bootstrap"; +import FormContainer from "../components/FormContainer"; +import Loader from "../components/Loader"; +import { useSelector } from "react-redux"; + +const AccInfoScreen = () => { + let currentAccount = useSelector((state) => state.account.current_account); + const { userInfo } = useSelector((state) => state.auth); + + if (!currentAccount) { + currentAccount = { + account_type: "", + account_number: "", + balance: "", + address: "", + govtId: "", + govtIdNo: "", + }; + } + + const [accType, setAccType] = useState(currentAccount.account_type); + const [accNo, setAccNo] = useState(currentAccount.account_number); + const [balance, setBalance] = useState(currentAccount.balance); + const [address, setAddress] = useState(currentAccount.address); + const [govtId, setGovtId] = useState(currentAccount.government_id_type); + const [govtIdNo, setGovtIdNo] = useState(currentAccount.govt_id_number); + + const isLoading = false; + + const submitHandler = async (e) => { + e.preventDefault(); + // try { + // const res = await login({ email, password }).unwrap(); + // dispatch(setCredentials({ ...res })); + // toast.success('Successfully logged in!') + // navigate('/'); + // } catch (err) { + // toast.error(err?.data?.message || err.error); + // } + }; + + return ( + +

+ Account Information +

+ +
+ + + + Account type + + + + + + Account number + + + + + + + + + Name + + + + + + Email address + + + + + + + + + Govt. ID + + + + + + + + + + + Govt. ID number + + + + + + + + + Balance + + + + + + Address + setAddress(e.target.value)} + > + + + + + + + + + + + + + + +
+ + {isLoading && } +
+ ); +}; + +export default AccInfoScreen; diff --git a/ui/src/screens/ApplyLoanScreen.jsx b/ui/src/screens/ApplyLoanScreen.jsx new file mode 100644 index 0000000..3b30c44 --- /dev/null +++ b/ui/src/screens/ApplyLoanScreen.jsx @@ -0,0 +1,502 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState, useEffect } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { + Form, + Button, + Row, + Col, + Modal, +} from "react-bootstrap"; +import FormContainer from "../components/FormContainer"; +import { useDispatch, useSelector } from "react-redux"; +import { + usePostLoanMutation, + useGetApprovedLoansMutation, +} from "../slices/loanApiSlice"; +import { createLoan } from "../slices/loanSlice"; +import { useGetAllAccountsMutation } from "../slices/accountApiSlice"; +import { getAccounts } from "../slices/accountSlice"; +import { toast } from "react-toastify"; +import Loader from "../components/Loader"; +import "../index.css"; + +const ApplyLoan = () => { + const [validated, setValidated] = useState(false); + + const [accNo, setAccNo] = useState(""); + const [accType, setAccType] = useState(""); + + const [govtId, setGovtId] = useState(""); + const [govtIdNo, setGovtIdNo] = useState(""); + + const [loanType, setLoanType] = useState(""); + const [loanAmount, setLoanAmount] = useState(""); + + const [intRate, setIntRate] = useState(""); + const [loanTime, setLoanTime] = useState(""); + + const [isCheckboxChecked, setIsCheckboxChecked] = useState(false); + const [loanAdded, setLoanAdded] = useState(false); + + let allAccounts = useSelector((state) => state.account.all_accounts).response; + if (!allAccounts) { + allAccounts = []; + } + + const [selectedAccount, setSelectedAccount] = useState(""); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const terms = { + BaseCamp: { + interestRate: 5.99, + timePeriod: 10, + }, + Rover: { + interestRate: 6.5, + timePeriod: 5, + }, + PotatoFarming: { + interestRate: 7.25, + timePeriod: 7, + }, + IceHome: { + interestRate: 8.75, + timePeriod: 15, + }, + Rocket: { + interestRate: 9.99, + timePeriod: 20, + }, + }; + + const [postLoanAPI, { isLoading }] = usePostLoanMutation(); + const [getAllAccounts, { isLoading: isLoading1 }] = + useGetAllAccountsMutation(); + + const { userInfo } = useSelector((state) => state.auth); + + const submitHandler = async (e) => { + e.preventDefault(); + e.stopPropagation(); + setValidated(true); + + const form = e.currentTarget; + if (form.checkValidity()) { + try { + const data_loan = new FormData(); + data_loan.append("name", userInfo.name); + data_loan.append("email", userInfo.email); + data_loan.append("account_number", accNo); + data_loan.append("account_type", accType); + data_loan.append("govt_id_number", govtIdNo); + data_loan.append("govt_id_type", govtId); + data_loan.append("loan_type", loanType); + data_loan.append("loan_amount", loanAmount); + data_loan.append("interest_rate", intRate); + data_loan.append("time_period", loanTime); + const res = await postLoanAPI(data_loan).unwrap(); + console.log(res); + dispatch(createLoan(res)); + toast.success("Congratulations! Your loan is approved!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + navigate("/loan"); + setLoanAdded(true); + } catch (err) { + console.log(err); + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + } + }; + + const [showModal, setShowModal] = useState(false); + + const handleModalOpen = () => { + setShowModal(true); + }; + + const handleModalClose = () => { + setShowModal(false); + }; + + const fetchAccounts = async () => { + const data = new FormData(); + data.append("email_id", userInfo.email); + const res = await getAllAccounts(data).unwrap(); + dispatch(getAccounts(res)); + }; + + useEffect(() => { + try { + fetchAccounts(); + } catch (err) { + console.log(err); + toast.error("Error in fetching accounts!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }, []); + + return ( + +

+ Loan Application +

+ {isLoading ? ( + + ) : ( +
+ + + + Name + + + + + + Email address + + + + + + + + + Account type + setAccType(e.target.value)} + aria-label="Select account type" + disabled={accType ? true : false} + > + + + + + + + + + + + Account number + { + const selectedAccountNumber = e.target.value; + const selectedAccount = allAccounts.find( + (account) => + account.account_number === selectedAccountNumber + ); + setAccNo(selectedAccountNumber); + setAccType( + selectedAccount ? selectedAccount.account_type : null + ); + setGovtId( + selectedAccount + ? selectedAccount.government_id_type + : null + ); + setGovtIdNo( + selectedAccount ? selectedAccount.govt_id_number : null + ); + }} + aria-label="Select account number" + > + + {allAccounts.map((account) => ( + + ))} + + + + + + + + + ID Type + setGovtId(e.target.value)} + aria-label="Select your ID type" + disabled={govtId ? true : false} + > + + + + + + + + + + ID number + setGovtIdNo(e.target.value)} + disabled={govtIdNo ? true : false} + /> + + + + + + + + Loan type + { + const selectedLoanType = e.target.value; + setIntRate(terms[selectedLoanType].interestRate); + setLoanTime(terms[selectedLoanType].timePeriod); + setLoanType(e.target.value); + }} + aria-label="Select loan type" + > + + + + + + + + + + + + Loan amount + setLoanAmount(e.target.value)} + onWheel={(e) => e.target.blur()} + > + + + + + + + + Interest Rate + e.target.blur()} + onChange={(e) => setIntRate(e.target.value)} + aria-label="Select loan type" + > + + + + + Time Period + setLoanTime(e.target.value)} + onWheel={(e) => e.target.blur()} + > + + + + + + +
+ + setIsCheckboxChecked(e.target.checked)} + required + /> + +
+ Terms and Conditions +
+ + + Terms and Conditions + + +

+ Welcome to Martian Bank! By applying for a loan with us, + you agree to the following terms and conditions: +

+ +

1. Eligibility

+

+ To apply for a loan at Martian Bank, you must meet certain + eligibility criteria. These criteria include but are not + limited to: being a resident of Mars, having a minimum age + of 21 years, and meeting the required creditworthiness + standards set by Martian Bank. Additional documentation + and information may be required during the loan + application process. +

+ +

2. Loan Terms

+

+ The loan terms, including the loan amount, interest rate, + repayment period, and any applicable fees, will be + provided to you during the loan application process. It is + important to carefully review and understand these terms + before accepting the loan offer. Any changes to the loan + terms will be communicated to you in a timely manner. +

+ +

3. Repayment

+

+ As a borrower, you are responsible for making timely + repayments as agreed upon in the loan agreement. Failure + to make payments on time may result in additional charges, + penalties, and potential damage to your credit history. It + is essential to manage your finances responsibly and + ensure sufficient funds are available for loan repayments. +

+ +

4. Default and Remedies

+

+ If you default on your loan payments, Martian Bank + reserves the right to take necessary actions to recover + the outstanding amount. This may include but is not + limited to reporting the default to credit agencies, + initiating legal proceedings, and engaging third-party + collection agencies. It is crucial to communicate with + Martian Bank in case of financial difficulties to explore + possible solutions and avoid default. +

+ +

+ By applying for a loan with Martian Bank, you acknowledge + that you have read, understood, and agreed to these Terms + and Conditions. If you have any questions or concerns, + please contact our customer support team. +

+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ )} +
+ ); +}; + +export default ApplyLoan; diff --git a/ui/src/screens/AtmScreen.jsx b/ui/src/screens/AtmScreen.jsx new file mode 100644 index 0000000..6417e77 --- /dev/null +++ b/ui/src/screens/AtmScreen.jsx @@ -0,0 +1,327 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState, useEffect } from "react"; +import { toast } from "react-toastify"; +import { divIcon } from "leaflet"; +import { useDispatch } from "react-redux"; +import { + useGetAtmsMutation, + useGetParticularATMMutation, +} from "../slices/atmsApiSlice"; +import { setAtms } from "../slices/atmSlice"; +import { + Container, + Form, + Button, + Card, + Row, + Col, + Badge, +} from "react-bootstrap"; +import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faAngleDown, faAngleUp } from "@fortawesome/free-solid-svg-icons"; +import "leaflet/dist/leaflet.css"; +import "../index.css"; +// import mapIcon from "../assets/coin.png"; +// import mapImg from "../assets/mapPlanets.png"; + +const AtmScreen = () => { + const [location, setLocation] = useState(""); + const [finalLocation, setFinalLocation] = useState(""); + const [atmsList, setAtmsList] = useState([]); + const [isOpenNow, setIsOpenNow] = useState(false); + const [isInterPlanetary, setIsInterPlanetary] = useState(false); + + const dispatch = useDispatch(); + + const [getAtmsList, { isLoading }] = useGetAtmsMutation(); + const [getParticularATM, { isLoading1 }] = useGetParticularATMMutation(); + + const [selectedCard, setSelectedCard] = useState(null); + const [selectedCardInfo, setSelectedCardInfo] = useState(null); + + const handleCardClick = async (atm) => { + if (selectedCard === atm) { + setSelectedCard(null); + setSelectedCardInfo(null); + } else { + const res = await getParticularATM(atm._id).unwrap(); + setSelectedCard(atm); + setSelectedCardInfo(res); + } + }; + + const handleSubmit = async (e) => { + setFinalLocation(location); + + try { + e.preventDefault(); + } catch (err) { + console.log(err); + } + + if (location === "") { + return; + } + + try { + console.log("here"); + console.log({ location, isOpenNow, isInterPlanetary }); + const res = await getAtmsList({ + location, + isOpenNow, + isInterPlanetary, + }).unwrap(); + console.log(res); + dispatch(setAtms(res)); + setAtmsList(res); + toast.success("Found ATMs near you!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } catch (err) { + console.log(err); + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }; + + useEffect(() => { + handleSubmit(); + }, [isOpenNow, isInterPlanetary]); + + // Create a custom Leaflet divIcon + const customIcon = divIcon({ + className: "custom-icon", + iconSize: [45, 45], + iconAnchor: [22.5, 45], + popupAnchor: [0, -45], + html: `
`, + }); + + return ( + +
+ + + + setLocation(e.target.value)} + /> + + + + + + + + + {/* Radio button for "Open Now" */} + { + setIsOpenNow(true); + setIsInterPlanetary(false); // Deselect the other radio button + }} + /> + {/* Radio button for "Inter planet ATMs" */} + { + setIsInterPlanetary(true); + setIsOpenNow(false); // Deselect the other radio button + }} + /> + + + + +
+ + {atmsList && atmsList.length > 0 ? ( +
+
Showing results for {finalLocation}:
+ + +
+ {atmsList.map((atm, index) => ( + handleCardClick(atm)} + > + + {atm.isOpen ? "Open" : "Closed"} + +
+ + + + logo + + {atm.name} + {selectedCard === atm ? ( + + ) : ( + + )} + + + {atm.address.street + + ", " + + atm.address.city + + ", " + + atm.address.state + + ", " + + atm.address.zip} + + + {selectedCard === atm && ( + + + +
+ Number of ATMs: {" "} + {selectedCardInfo.numberOfATMs} +
+
+ Accessibility: + {selectedCardInfo.atmHours} +
+ + +
+ Timings: +
+
+ Mon-Fri: {selectedCardInfo.timings.monFri} +
+
+ Sat-Sun: {selectedCardInfo.timings.satSun} +
+
+ Holidays: {selectedCardInfo.timings.holidays} +
+ +
+
+ )} +
+
+ ))} +
+ + {isInterPlanetary ? ( + +
+ Map +
+ + ) : ( + +
+ + + {atmsList.map((atm, index) => ( + + + {atm.name} + + + ))} + +
+ + )} +
+
+ ) : null} +
+ ); +}; + +export default AtmScreen; diff --git a/ui/src/screens/HomeScreen.jsx b/ui/src/screens/HomeScreen.jsx new file mode 100644 index 0000000..0fcd3c0 --- /dev/null +++ b/ui/src/screens/HomeScreen.jsx @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { Link } from "react-router-dom"; +import { Container, Row, Col, Card, Button } from "react-bootstrap"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRightFromBracket } from "@fortawesome/free-solid-svg-icons"; +import { toast } from "react-toastify"; +import { useGetAllAccountsMutation } from "../slices/accountApiSlice"; +import { + getAccounts, + selectedAccount, + currentAccount, +} from "../slices/accountSlice"; +import Loader from "../components/Loader"; +import AccountDisclosure from "../components/AccountDisclosure"; +import Hero from "../components/Hero"; +import "../index.css"; + +const HomeScreen = () => { + const { userInfo } = useSelector((state) => state.auth); + const accountInfo = useSelector( + (state) => state.account.all_accounts + ).response; + const dispatch = useDispatch(); + const [getAllAccounts, { isLoading }] = useGetAllAccountsMutation(); + + useEffect(() => { + try { + dispatch(selectedAccount()); + dispatch(currentAccount()); + fetchAccounts(); + } catch (err) { + console.log(err); + toast.error("Error in fetching accounts!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }, []); + + const fetchAccounts = async () => { + const data = new FormData(); + data.append("email_id", userInfo.email); + const res = await getAllAccounts(data).unwrap(); + console.log(res); + dispatch(getAccounts(res)); + }; + + const renderAccountCard = (account) => { + return ( + dispatch(currentAccount(account))} + > + + + {account.account_type} Account + + + + + + +
+ + ${account.balance.toFixed(2)} + +
+
Available balance
+ + + +
+ Account Number: + +   + ...{account.account_number.slice(-4)} + +
+
+ Name: {account.name}
+ Email ID: {account.email_id} +
+
+ +
+
+
+ + dispatch(selectedAccount(account))} + > + + + +
+ + ); + }; + + const renderAccounts = () => { + if (!accountInfo) { + return ; + } + + if (accountInfo.length > 0) { + return accountInfo.map((account) => renderAccountCard(account)); + } else { + return ( + <> + + + No Accounts Found + + + + Would you like to create a new account with us? + + + + + + + + + + ); + } + }; + + const renderDashboard = () => { + return ( + + + + + {renderAccounts()} + + + + + + + + ); + }; + + return userInfo ? renderDashboard() : ; +}; + +export default HomeScreen; diff --git a/ui/src/screens/LoanScreen.jsx b/ui/src/screens/LoanScreen.jsx new file mode 100644 index 0000000..a659904 --- /dev/null +++ b/ui/src/screens/LoanScreen.jsx @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { + Button, + Row, + Col, + Card, + Badge, +} from "react-bootstrap"; +import { useDispatch, useSelector } from "react-redux"; +import { + useGetApprovedLoansMutation, +} from "../slices/loanApiSlice"; +import { storeLoanHistory } from "../slices/loanSlice"; +import { toast } from "react-toastify"; +import Loader from "../components/Loader"; +import "../index.css"; + +const CustomCard = ({ title, text, icon, link }) => { + return ( + + + + {title} + + + {text} + + + + + ); +}; + +const LoanScreen = () => { + + const [loanAdded, setLoanAdded] = useState(false); + + const loanInfo = useSelector((state) => state.loan.loan_history).response; + + let allAccounts = useSelector((state) => state.account.all_accounts).response; + if (!allAccounts) { + allAccounts = []; + } + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const [loanHistoryAPI, { isLoading: isLoading2 }] = + useGetApprovedLoansMutation(); + + const { userInfo } = useSelector((state) => state.auth); + + const fetchLoans = async () => { + const data = new FormData(); + data.append("email", userInfo.email); + const res = await loanHistoryAPI(data).unwrap(); + console.log(res); + dispatch(storeLoanHistory(res)); + }; + + useEffect(() => { + try { + fetchLoans(); + } catch (err) { + console.log(err); + toast.error("Error in fetching loans!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }, [loanAdded]); + + return ( + + +
+ Loan options for you +
+ + Interest Rate: 5.99%, Time Period: 10 years
+ + Eligible + + + } + /> + + Interest Rate: 6.5%, Time Period: 5 years
+ + Eligible + + + } + /> + + + + + + + + + + + + + {loanInfo ? ( + loanInfo.length > 0 ? ( +
+
+ Enrolled Loans +
+ {loanInfo.map((loan) => ( + + Interest Rate: {loan.interest_rate}%, Time Period: + {loan.time_period} years
+ Account: {loan.account_number} + + } + /> + ))} +
+ ) : ( +
+
+ Enrolled Loans +
+

+ You dont have any approved loans +

+
+ ) + ) : ( + + )} + +
+ ); +}; + +export default LoanScreen; diff --git a/ui/src/screens/LoginScreen.jsx b/ui/src/screens/LoginScreen.jsx new file mode 100644 index 0000000..220f477 --- /dev/null +++ b/ui/src/screens/LoginScreen.jsx @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState, useEffect } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { Form, Button, Row, Col, Container } from "react-bootstrap"; +import { useDispatch, useSelector } from "react-redux"; +import { useLoginMutation } from "../slices/usersApiSlice"; +import { setCredentials } from "../slices/authSlice"; +import { toast } from "react-toastify"; +import Loader from "../components/Loader"; +import "../index.css"; + +const LoginScreen = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const [login, { isLoading }] = useLoginMutation(); + + const { userInfo } = useSelector((state) => state.auth); + + useEffect(() => { + if (userInfo) { + navigate("/"); + } + }, [navigate, userInfo]); + + const submitHandler = async (e) => { + e.preventDefault(); + try { + const res = await login({ email, password }).unwrap(); + console.log(res); + if (res.status === false) { + toast.error(res.message, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + return; + } + dispatch(setCredentials({ ...res })); + toast.success("Successfully logged in!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + navigate("/"); + } catch (err) { + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }; + + return ( + + + + + +

+ Login +

+ +
+ + setEmail(e.target.value)} + > + + Please enter a valid email address. + + + + + setPassword(e.target.value)} + > + + Password must include: +
1. at least 8 characters
+
2. at least one uppercase letter
+
3. at least one lowercase letter
+
4. at least one digit
+
5. at least one special character (@$!%*#?&)
+
+
+ + +
+ + {isLoading && } + + + + New Customer? Create new account + + + +
+ + + + + +

$100 bonus on us!

+

+ Open an eligible account with qualifying electronic deposits and + get $100 bonus. +

+ +
+ + + card + + + +
+
+ ); +}; + +export default LoginScreen; diff --git a/ui/src/screens/NewAccScreen.jsx b/ui/src/screens/NewAccScreen.jsx new file mode 100644 index 0000000..9220d2d --- /dev/null +++ b/ui/src/screens/NewAccScreen.jsx @@ -0,0 +1,321 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { Form, Button, Row, Col, Modal, Container } from "react-bootstrap"; +import { useDispatch, useSelector } from "react-redux"; +import { useCreateAccountMutation } from "../slices/accountApiSlice"; +import { createAccount } from "../slices/accountSlice"; +import { toast } from "react-toastify"; +import Loader from "../components/Loader"; +import "../index.css"; +import TermsAndConditionsModal from "../components/AccountTnC"; + +const NewAccScreen = () => { + const [validated, setValidated] = useState(false); + const [address, setAddress] = useState(""); + const [govtId, setGovtId] = useState(""); + const [govtIdNo, setGovtIdNo] = useState(""); + const [accType, setAccType] = useState(""); + const [isCheckboxChecked, setIsCheckboxChecked] = useState(false); + const [showModal, setShowModal] = useState(false); + + const navigate = useNavigate(); + const dispatch = useDispatch(); + const { userInfo } = useSelector((state) => state.auth); + const [createNewAccount, { isLoading }] = useCreateAccountMutation(); + + const handleSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + setValidated(true); + + const form = e.currentTarget; + if (form.checkValidity()) { + try { + // Form data submission + const data = new FormData(); + data.append("name", userInfo.name); + data.append("email_id", userInfo.email); + data.append("address", address); + data.append("government_id_type", govtId); + data.append("govt_id_number", govtIdNo); + data.append("account_type", accType); + console.log("Sending data: ", { + name: userInfo.name, + email_id: userInfo.email, + address, + government_id_type: govtId, + govt_id_number: govtIdNo, + account_type: accType, + }) + + const res = await createNewAccount(data).unwrap(); + if (res.response) { + dispatch(createAccount(res)); + toast.success( + "Congratulations, your account has been created! We have also given you a $100 joining bonus", + { + className: "toast-container-custom", + autoClose: 2000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + } + ); + navigate("/"); + } else { + toast.error(`You can only have 1 ${accType} account`, { + className: "toast-container-custom", + autoClose: 2000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + } catch (err) { + console.log(err); + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + } + }; + + return ( +
+ + + {/* Left Column */} + + + +

+ New Account +

+ {isLoading ? ( + + ) : ( +
+ + + + Name + + + + + + Email address + + + + + + + + Account type + setAccType(e.target.value)} + aria-label="Select account type" + > + + + + + + + + + + + + + ID type + setGovtId(e.target.value)} + aria-label="Select your ID type" + > + + + + + + + + + + ID number + setGovtIdNo(e.target.value)} + /> + + + + + + + Address + setAddress(e.target.value)} + /> + + + + + +
+ + + setIsCheckboxChecked(e.target.checked) + } + required + /> + +
setShowModal(true)} + style={{ + textDecoration: "underline", + color: "blue", + }} + > + Terms and Conditions +
+ {(showModal || !isCheckboxChecked) && ( + setShowModal(false)} + centered + size="xl" + > + {showModal && } + + + + + )} +
+ +
+ + + + + + + + + + + + +
+ )} + +
+ + + {/* Right Column */} + + + +

$100 bonus on us!

+

+ Open an eligible account with qualifying electronic deposits + and get $100 bonus. +

+ +
+ + + card + + + +
+
+
+ ); +}; + +export default NewAccScreen; diff --git a/ui/src/screens/ProfileScreen.jsx b/ui/src/screens/ProfileScreen.jsx new file mode 100644 index 0000000..d6a9ee0 --- /dev/null +++ b/ui/src/screens/ProfileScreen.jsx @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { Form, Button } from "react-bootstrap"; +import { useDispatch, useSelector } from "react-redux"; +import FormContainer from "../components/FormContainer"; +import { toast } from "react-toastify"; +import Loader from "../components/Loader"; +import { useUpdateUserMutation } from "../slices/usersApiSlice"; +import { setCredentials } from "../slices/authSlice"; +import "../index.css"; + +const ProfileScreen = () => { + const [email, setEmail] = useState(""); + const [name, setName] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const { userInfo } = useSelector((state) => state.auth); + + const [updateProfile, { isLoading }] = useUpdateUserMutation(); + + useEffect(() => { + setName(userInfo.name); + setEmail(userInfo.email); + }, [userInfo.email, userInfo.name]); + + const submitHandler = async (e) => { + e.preventDefault(); + if (password !== confirmPassword) { + toast.error("Passwords do not match"); + } else { + try { + const res = await updateProfile({ + _id: userInfo._id, + name, + email, + password, + // token: Cookies.get("jwt"), + }).unwrap(); + dispatch(setCredentials(res)); + toast.success("Your profile has been updated", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + navigate("/"); + } catch (err) { + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + } + }; + return ( + +

+ UPDATE   DETAILS +

+ +
+ + Name + setName(e.target.value)} + > + + + Email Address + setEmail(e.target.value)} + > + + + Password + setPassword(e.target.value)} + > + + + + Confirm Password + setConfirmPassword(e.target.value)} + > + + + + + {isLoading && } + +
+ ); +}; + +export default ProfileScreen; diff --git a/ui/src/screens/RegisterScreen.jsx b/ui/src/screens/RegisterScreen.jsx new file mode 100644 index 0000000..0aaecdf --- /dev/null +++ b/ui/src/screens/RegisterScreen.jsx @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState, useEffect } from "react"; +import { Form, Button, Row, Col, Container } from "react-bootstrap"; +import Loader from "../components/Loader"; +import { Link, useNavigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { useRegisterMutation } from "../slices/usersApiSlice"; +import { setCredentials } from "../slices/authSlice"; +import { toast } from "react-toastify"; +import "../index.css"; + +const RegisterScreen = () => { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const [register, { isLoading }] = useRegisterMutation(); + + const { userInfo } = useSelector((state) => state.auth); + + useEffect(() => { + if (userInfo) { + if (userInfo.email){ + navigate("/"); + } + } + }, [navigate, userInfo]); + + const submitHandler = async (e) => { + e.preventDefault(); + + if (password !== confirmPassword) { + toast.error("Passwords do not match"); + } else { + try { + const res = await register({ name, email, password }).unwrap(); + console.log(res); + dispatch(setCredentials({ ...res })); + toast.success( + "Congratulations! Your account with Martian Bank has been created.", + { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + } + ); + navigate("/login"); + } catch (err) { + console.log(err); + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + } + }; + return ( + + + + + +

+ Make an Account +

+
+ + setName(e.target.value)} + /> + + Please enter your full name. + + + + + setEmail(e.target.value)} + > + + Please enter a valid email address. + + + + + setPassword(e.target.value)} + > + + Password must include: +
1. at least 8 characters
+
2. at least one uppercase letter
+
3. at least one lowercase letter
+
4. at least one digit
+
5. at least one special character (@$!%*#?&)
+
+
+ + setConfirmPassword(e.target.value)} + > + + + + + {isLoading && } + + + + + Already have an account? Login + + + +
+ + + + +

$100 bonus on us!

+

+ Open an eligible account with qualifying electronic deposits and + get $100 bonus. +

+ +
+ + + card + + + +
+
+ ); +}; + +export default RegisterScreen; diff --git a/ui/src/screens/TransactionScreen.jsx b/ui/src/screens/TransactionScreen.jsx new file mode 100644 index 0000000..dc8c4b5 --- /dev/null +++ b/ui/src/screens/TransactionScreen.jsx @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import React from "react"; +import { + MDBBadge, + MDBTable, + MDBTableHead, + MDBTableBody, +} from "mdb-react-ui-kit"; +import { Container, Form, Row, Col } from "react-bootstrap"; +import { toast } from "react-toastify"; +import { useGetTransactionsMutation } from "../slices/transactionApiSlice"; +import { useGetAllAccountsMutation } from "../slices/accountApiSlice"; +import { getAccounts } from "../slices/accountSlice"; +import { storeTransaction } from "../slices/transactionSlice"; +import { useState, useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import "../index.css"; + +const TransactionScreen = () => { + const dispatch = useDispatch(); + + const { userInfo } = useSelector((state) => state.auth); + + const [allAccounts, setAllAccounts] = useState([]); + + const [selectedAccount, setSelectedAccount] = useState(""); + const [history, setHistory] = useState([]); + + const [getTransactions, { isLoading }] = useGetTransactionsMutation(); + const [getAllAccounts, { isLoading: isLoading1 }] = + useGetAllAccountsMutation(); + + const fetchHistory = async (e) => { + setSelectedAccount(e.target.value); + e.preventDefault(); + try { + const data = new FormData(); + data.append("account_number", e.target.value); + const res = await getTransactions(data).unwrap(); + console.log(res) + dispatch(storeTransaction(res)); + setHistory(res.response); + } catch (err) { + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }; + + const fetchAccounts = async () => { + const acc_data = new FormData(); + acc_data.append("email_id", userInfo.email); + const res = await getAllAccounts(acc_data).unwrap(); + dispatch(getAccounts(res)); + setAllAccounts(res.response); + }; + + useEffect(() => { + try { + fetchAccounts(); + } catch (err) { + console.log(err); + toast.error("Error in fetching accounts!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }, []); + + return ( + +
+ + + + + + {allAccounts.map((account) => ( + + ))} + + + + +
+ + + + + Sender Account + + + Amount + + + Details + + + Type + + + + + {history && history.length > 0 ? ( + history.map((transaction) => ( + <> + + + {transaction.account_number} + + + $ {transaction.amount} + + +

+ {transaction.time_stamp.substring(0, 10)} +

+

{transaction.reason}

+ + + + {transaction.type} + + + + + )) + ) : selectedAccount ? ( + + + No transactions found. + + + ) : ( + + + Please select an account. + + + )} +
+
+
+ ); +}; + +export default TransactionScreen; diff --git a/ui/src/screens/TransferScreen.jsx b/ui/src/screens/TransferScreen.jsx new file mode 100644 index 0000000..45edf31 --- /dev/null +++ b/ui/src/screens/TransferScreen.jsx @@ -0,0 +1,612 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { useState, useEffect } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { + Form, + Button, + Row, + Col, + DropdownButton, + Dropdown, + Tabs, + Tab, +} from "react-bootstrap"; +import FormContainer from "../components/FormContainer"; +import { useSelector, useDispatch } from "react-redux"; +import { + usePostTransferMutation, + usePostTransferExternalMutation, +} from "../slices/transferApiSlice"; +import { createTransfer } from "../slices/transferSlice"; +import { deleteSelectedAccount } from "../slices/accountSlice"; +import { useGetAllAccountsMutation } from "../slices/accountApiSlice"; +import { getAccounts } from "../slices/accountSlice"; +import { toast } from "react-toastify"; +import Loader from "../components/Loader"; +import "../index.css"; + +const TransferScreen = () => { + let selectedAccount = useSelector((state) => state.account.selected_account); + let allAccounts = useSelector((state) => state.account.all_accounts).response; + + if (!selectedAccount) { + selectedAccount = { + accountType: "", + accountNumber: "", + balance: 0.0, + }; + } + + if (!allAccounts) { + allAccounts = []; + } + + const [transferType, setTransferType] = useState("internal"); + const [accType, setAccType] = useState( + selectedAccount ? selectedAccount.account_type : "" + ); + const [accNo, setAccNo] = useState( + selectedAccount ? selectedAccount.account_number : "" + ); + const [receiverEmail, setReceiverEmail] = useState(""); + const [receiverAcc, setReceiverAcc] = useState(""); + const [receiverAccNo, setReceiverAccNo] = useState(""); + const [transferAmount, setTransferAmount] = useState(""); + const [reason, setReason] = useState(""); + const [balance, setBalance] = useState(selectedAccount.balance); + + const checkingAccount = allAccounts.find( + (account) => account.account_type === "Checking" + ); + + const checkingAccountBalance = checkingAccount + ? checkingAccount.balance + : null; + + const { userInfo } = useSelector((state) => state.auth); + const toggleTransferType = (key) => { + setTransferType(key); + setReason(""); + setTransferAmount(""); + }; + + const navigate = useNavigate(); + const dispatch = useDispatch(); + + const [postTransfer, { isLoading }] = usePostTransferMutation(); + const [postTransferExternal, { isLoading: isLoading2 }] = + usePostTransferExternalMutation(); + const [getAllAccounts, { isLoading: isLoading1 }] = + useGetAllAccountsMutation(); + + const submitHandler = async (e) => { + e.preventDefault(); + + if (accNo === receiverAccNo) { + toast.error("Sender and receiver account numbers cannot be same!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + return; + } + + try { + const data = new FormData(); + data.append("sender_account_number", accNo); + data.append("receiver_account_number", receiverAccNo); + data.append("sender_account_type", accType); + data.append("receiver_account_type", receiverAcc); + data.append("reason", reason); + data.append("amount", transferAmount); + + const res = await postTransfer(data).unwrap(); + console.log(res); + dispatch(createTransfer({ ...res })); + toast.success("Money transfered!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + dispatch(deleteSelectedAccount()); + navigate("/"); + } catch (err) { + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }; + + const submitHandlerExternal = async (e) => { + e.preventDefault(); + if (userInfo.email === receiverEmail) { + toast.error("Sender and receiver cannot be same!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + return; + } + + try { + const data_external = new FormData(); + data_external.append("sender_email", userInfo.email); + data_external.append("receiver_email", receiverEmail); + data_external.append("reason", reason); + data_external.append("amount", transferAmount); + + const res = await postTransferExternal(data_external).unwrap(); + console.log(res); + if (res?.response?.approved === false) { + toast.error(res.response.message, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + return; + } + else if (res?.response?.approved === true) { + toast.success("Money transfered!", { + className: "toast-container-custom", + autoClose: false, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + navigate("/"); + } + } catch (err) { + toast.error(err?.data?.message || err.error, { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }; + + const fetchAccounts = async () => { + const data = new FormData(); + data.append("email_id", userInfo.email); + const res = await getAllAccounts(data).unwrap(); + dispatch(getAccounts(res)); + }; + + useEffect(() => { + try { + fetchAccounts(); + } catch (err) { + console.log(err); + toast.error("Error in fetching accounts!", { + className: "toast-container-custom", + autoClose: 500, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "dark", + }); + } + }, []); + + return ( + +

+ Transfer Money +

+ + + + + +
+ + + setAccType(option)} + style={{ width: "100%" }} + > + + Your account type + + Savings + + Checking + + + Investment + + + Money Market + + + + + + Sender account number + + { + const selectedAccountNumber = e.target.value; + const selectedAccount = allAccounts.find( + (account) => + account.account_number === selectedAccountNumber + ); + setAccNo(selectedAccountNumber); + setAccType( + selectedAccount ? selectedAccount.account_type : null + ); + setBalance( + selectedAccount ? selectedAccount.balance : null + ); + }} + style={{ width: "100%" }} + disabled={accNo ? true : false} + > + + {allAccounts.map((account) => { + if (account.account_number !== receiverAccNo) { + return ( + + ); + } + return null; + })} + + + + + + + setReceiverAcc(e)} + style={{ width: "100%" }} + disabled={receiverAccNo ? true : false} + > + + Receiver account type + + Savings + + Checking + + + Investment + + + Money Market + + + + + + Receiver account number + { + const r_selectedAccountNumber = e.target.value; + const r_selectedAccount = allAccounts.find( + (account) => + account.account_number === r_selectedAccountNumber + ); + setReceiverAccNo(r_selectedAccountNumber); + setReceiverAcc( + r_selectedAccount + ? r_selectedAccount.account_type + : null + ); + }} + style={{ width: "100%" }} + > + + {allAccounts.map((account) => { + if (account.account_number !== accNo) { + return ( + + ); + } + return null; + })} + + + + + + + {selectedAccount.balance !== "" ? ( + <> + + + Your balance + + + + + + Amount to be transferred + setTransferAmount(e.target.value)} + onWheel={(e) => e.target.blur()} + /> + + + + ) : ( + + + Amount to be transferred + setTransferAmount(e.target.value)} + onWheel={(e) => e.target.blur()} + /> + + + )} + + + + + Reason + setReason(e.target.value)} + > + + + + + + + + + + + + + +
+
+ + + {checkingAccount ? ( +
+ + + + + + Sender email ID + + + + + + + + + + + Receiver email ID + setReceiverEmail(e.target.value)} + style={{ width: "100%" }} + > + + + + + + + + Your balance + + + + + + Amount to be transferred + setTransferAmount(e.target.value)} + onWheel={(e) => e.target.blur()} + /> + + + + + + + Reason + setReason(e.target.value)} + > + + + + + + + + + + + + + + +
+ ) : ( +
+ You do not have a checking account. Please create one to make + external transfers. +
+ )} +
+
+ + +
+ + {isLoading && } +
+ ); +}; + +export default TransferScreen; diff --git a/ui/src/slices/accountApiSlice.js b/ui/src/slices/accountApiSlice.js new file mode 100644 index 0000000..c89afb4 --- /dev/null +++ b/ui/src/slices/accountApiSlice.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { apiSlice } from "./usersApiSlice"; +import ApiUrls from "./apiUrls"; + +const accsUrl = import.meta.env.VITE_ACCOUNTS_URL || ApiUrls.VITE_ACCOUNTS_URL; + +export const accountApiSlice = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + createAccount: builder.mutation({ + query: (data) => ({ + url: `${accsUrl}create`, + method: "POST", + prepareHeaders: (headers) => { + headers.set("Content-Type", "multipart/form-data"); + return headers; + }, + body: data, + }), + }), + getAllAccounts: builder.mutation({ + query: (data) => ({ + url: `${accsUrl}allaccounts`, + method: "POST", + body: data, + prepareHeaders: (headers) => { + headers.set("Content-Type", "application/json"); + return headers; + }, + }), + }), + }), +}); + +export const { useCreateAccountMutation, useGetAllAccountsMutation } = accountApiSlice; diff --git a/ui/src/slices/accountSlice.js b/ui/src/slices/accountSlice.js new file mode 100644 index 0000000..4784edc --- /dev/null +++ b/ui/src/slices/accountSlice.js @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + new_account: [], + all_accounts: [], + selected_account: [], + current_account: [], + isLoading: false, + error: null, +}; + +const accountSlice = createSlice({ + name: "accounts", + initialState, + reducers: { + createAccount: (state, action) => { + state.new_account = action.payload; + }, + getAccounts: (state, action) => { + state.all_accounts = action.payload; + }, + selectedAccount: (state, action) => { + state.selected_account = action.payload; + }, + deleteSelectedAccount: (state, action) => { + state.selected_account = []; + }, + currentAccount: (state, action) => { + state.current_account = action.payload; + }, + deleteCurrentAccount: (state, action) => { + state.current_account = []; + }, + }, +}); + +export const { + createAccount, + getAccounts, + selectedAccount, + deleteSelectedAccount, + currentAccount, + deleteCurrentAccount, +} = accountSlice.actions; + +export default accountSlice.reducer; diff --git a/ui/src/slices/apiUrls.js b/ui/src/slices/apiUrls.js new file mode 100644 index 0000000..823a627 --- /dev/null +++ b/ui/src/slices/apiUrls.js @@ -0,0 +1,15 @@ +const VITE_USERS_URL = 'http://localhost:8000/api/users/'; +const VITE_ATM_URL = 'http://localhost:8001/api/atm/'; +const VITE_ACCOUNTS_URL = 'http://127.0.0.1:5000/account/'; +const VITE_TRANSFER_URL = 'http://127.0.0.1:5000/transaction/'; +const VITE_LOAN_URL = 'http://127.0.0.1:5000/loan/'; + +const ApiUrls = { + VITE_USERS_URL, + VITE_ATM_URL, + VITE_ACCOUNTS_URL, + VITE_TRANSFER_URL, + VITE_LOAN_URL, +}; + +export default ApiUrls; diff --git a/ui/src/slices/atmSlice.js b/ui/src/slices/atmSlice.js new file mode 100644 index 0000000..80f2835 --- /dev/null +++ b/ui/src/slices/atmSlice.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + atms: [], + isLoading: false, + error: null, +}; + +const atmSlice = createSlice({ + name: "atm", + initialState, + reducers: { + setAtms: (state, action) => { + state.atms = action.payload; + }, + }, +}); + +export const { setAtms } = atmSlice.actions; + +export default atmSlice.reducer; diff --git a/ui/src/slices/atmsApiSlice.js b/ui/src/slices/atmsApiSlice.js new file mode 100644 index 0000000..320eaf2 --- /dev/null +++ b/ui/src/slices/atmsApiSlice.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { apiSlice } from "./usersApiSlice"; +import ApiUrls from "./apiUrls"; + +const atmUrl = import.meta.env.VITE_ATM_URL || ApiUrls.VITE_ATM_URL; + +export const atmApiSlice = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + getAtms: builder.mutation({ + query: (data) => ({ + url: `${atmUrl}`, + method: "POST", + body: data, + }), + }), + getParticularATM: builder.mutation({ + query: (id) => ({ + url: `${atmUrl}${id}`, + method: "GET", + }), + }), + }), +}); + +export const { useGetAtmsMutation, useGetParticularATMMutation } = atmApiSlice; diff --git a/ui/src/slices/authSlice.js b/ui/src/slices/authSlice.js new file mode 100644 index 0000000..7a3af31 --- /dev/null +++ b/ui/src/slices/authSlice.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { createSlice } from '@reduxjs/toolkit'; +import Cookies from 'js-cookie'; + +const initialState = { + userInfo: localStorage.getItem('userInfo') + ? JSON.parse(localStorage.getItem('userInfo')) + : null, +}; + +const authSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + setCredentials: (state, action) => { + state.userInfo = action.payload; + localStorage.setItem('userInfo', JSON.stringify(action.payload)); + }, + logout: (state) => { + state.userInfo = null; + // Cookies.remove('jwt'); + localStorage.removeItem('userInfo'); + }, + }, +}); + +export const { setCredentials, logout } = authSlice.actions; + +export default authSlice.reducer; diff --git a/ui/src/slices/loanApiSlice.js b/ui/src/slices/loanApiSlice.js new file mode 100644 index 0000000..be38221 --- /dev/null +++ b/ui/src/slices/loanApiSlice.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { apiSlice } from "./usersApiSlice"; +import ApiUrls from "./apiUrls"; + +const loanUrl = import.meta.env.VITE_LOAN_URL || ApiUrls.VITE_LOAN_URL; + +export const loanApiSlice = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + postLoan: builder.mutation({ + query: (data) => ({ + url: `${loanUrl}`, + method: "POST", + body: data, + prepareHeaders: (headers) => { + headers.set("Content-Type", "multipart/form-data"); + return headers; + }, + }), + }), + getApprovedLoans: builder.mutation({ + query: (data) => ({ + url: `${loanUrl}history`, + method: "POST", + body: data, + prepareHeaders: (headers) => { + headers.set("Content-Type", "application/json"); + return headers; + }, + }), + }), + }), +}); + +export const { usePostLoanMutation, useGetApprovedLoansMutation } = loanApiSlice; diff --git a/ui/src/slices/loanSlice.js b/ui/src/slices/loanSlice.js new file mode 100644 index 0000000..65de82d --- /dev/null +++ b/ui/src/slices/loanSlice.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + applied_loan: [], + loan_history: [], + isLoading: false, + error: null, +}; + +const loanSlice = createSlice({ + name: "loan", + initialState, + reducers: { + createLoan: (state, action) => { + state.applied_loan = action.payload; + }, + storeLoanHistory: (state, action) => { + state.loan_history = action.payload; + }, + }, +}); + +export const { createLoan, storeLoanHistory } = loanSlice.actions; + +export default loanSlice.reducer; diff --git a/ui/src/slices/transactionApiSlice.js b/ui/src/slices/transactionApiSlice.js new file mode 100644 index 0000000..2f191fc --- /dev/null +++ b/ui/src/slices/transactionApiSlice.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { apiSlice } from "./usersApiSlice"; +import ApiUrls from "./apiUrls"; + +const transferUrl = import.meta.env.VITE_TRANSFER_URL || ApiUrls.VITE_TRANSFER_URL; + +export const transactionApiSlice = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + getTransactions: builder.mutation({ + query: (data) => ({ + url: `${transferUrl}history`, + method: "POST", + body: data + }), + }), + }), +}); + +export const { useGetTransactionsMutation } = transactionApiSlice; diff --git a/ui/src/slices/transactionSlice.js b/ui/src/slices/transactionSlice.js new file mode 100644 index 0000000..cca08cc --- /dev/null +++ b/ui/src/slices/transactionSlice.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + history: [], + isLoading: false, + error: null, +}; + +const transactionSlice = createSlice({ + name: "history", + initialState, + reducers: { + storeTransaction: (state, action) => { + state.history = action.payload; + }, + }, +}); + +export const { storeTransaction } = transactionSlice.actions; + +export default transactionSlice.reducer; diff --git a/ui/src/slices/transferApiSlice.js b/ui/src/slices/transferApiSlice.js new file mode 100644 index 0000000..adbc0e2 --- /dev/null +++ b/ui/src/slices/transferApiSlice.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { apiSlice } from "./usersApiSlice"; +import ApiUrls from "./apiUrls"; + +const transferUrl = import.meta.env.VITE_TRANSFER_URL || ApiUrls.VITE_TRANSFER_URL; + +export const transferApiSlice = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + postTransfer: builder.mutation({ + query: (data) => ({ + url: `${transferUrl}`, + method: "POST", + body: data + }), + }), + postTransferExternal: builder.mutation({ + query: (data) => ({ + url: `${transferUrl}zelle/`, + method: "POST", + body: data, + }), + }), + }), +}); + +export const { usePostTransferMutation, usePostTransferExternalMutation } = transferApiSlice; diff --git a/ui/src/slices/transferSlice.js b/ui/src/slices/transferSlice.js new file mode 100644 index 0000000..441165c --- /dev/null +++ b/ui/src/slices/transferSlice.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + current_transfer: [], + isLoading: false, + error: null, +}; + +const transferSlice = createSlice({ + name: "transfer", + initialState, + reducers: { + createTransfer: (state, action) => { + state.current_transfer = action.payload; + }, + }, +}); + +export const { createTransfer } = transferSlice.actions; + +export default transferSlice.reducer; diff --git a/ui/src/slices/usersApiSlice.js b/ui/src/slices/usersApiSlice.js new file mode 100644 index 0000000..f18f210 --- /dev/null +++ b/ui/src/slices/usersApiSlice.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { fetchBaseQuery, createApi } from "@reduxjs/toolkit/query/react"; +import ApiUrls from "./apiUrls"; + +const usersUrl = import.meta.env.VITE_USERS_URL || ApiUrls.VITE_USERS_URL; + +// const baseQuery = fetchBaseQuery({ baseUrl: 'http://host.docker.internal:8000/' }); +const baseQuery = fetchBaseQuery({ + baseUrl: "", +}); + +export const apiSlice = createApi({ + baseQuery, + tagTypes: ["User"], + endpoints: () => ({}), +}); + +export const userApiSlice = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + login: builder.mutation({ + query: (data) => ({ + url: `${usersUrl}/auth`, + method: "POST", + body: data, + // credentials: 'include', + }), + }), + logout: builder.mutation({ + query: (data) => ({ + url: `${usersUrl}/logout`, + method: "POST", + body: data, + headers: { + 'Content-Type': 'application/json', + }, + }), + }), + register: builder.mutation({ + query: (data) => ({ + url: `${usersUrl}`, + method: "POST", + body: data, + }), + }), + updateUser: builder.mutation({ + query: (data) => ({ + url: `${usersUrl}/profile`, + method: "PUT", + body: { + _id: data._id, + name: data.name, + email: data.email, + password: data.password, + }, + // headers: { + // 'Content-Type': 'application/json', + // 'Authorization': data.token + // }, + }), + }), + }), +}); + +export const { + useLoginMutation, + useLogoutMutation, + useRegisterMutation, + useUpdateUserMutation, +} = userApiSlice; diff --git a/ui/src/store.js b/ui/src/store.js new file mode 100644 index 0000000..e25409e --- /dev/null +++ b/ui/src/store.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { configureStore } from "@reduxjs/toolkit"; +import { apiSlice } from "./slices/usersApiSlice"; +import authReducer from "./slices/authSlice"; +import atmReducer from "./slices/atmSlice"; +import accountReducer from "./slices/accountSlice"; +import transferReducer from "./slices/transferSlice"; +import transactionReducer from "./slices/transactionSlice"; +import loanReducer from "./slices/loanSlice"; + +const store = configureStore({ + reducer: { + [apiSlice.reducerPath]: apiSlice.reducer, + atm: atmReducer, + auth: authReducer, + account: accountReducer, + transfer: transferReducer, + transaction: transactionReducer, + loan: loanReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat([apiSlice.middleware]), + devTools: true, +}); + +export default store; diff --git a/ui/vite.config.js b/ui/vite.config.js new file mode 100644 index 0000000..facc54c --- /dev/null +++ b/ui/vite.config.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2023 Cisco Systems, Inc. and its affiliates All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + // proxy: { + // '/auth': { + // target: 'http://host.docker.internal:8000', + // changeOrigin: true, + // }, + // '/atm': { + // target: 'http://host.docker.internal:8001', + // changeOrigin: true, + // }, + // }, + proxy: { + '/auth': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + '/atm': { + target: 'http://localhost:8001', + changeOrigin: true, + }, + }, + watch: { + usePolling: true, + }, + host: true, + }, +});