diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..28182707 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,69 @@ +version: 2.1 + +orbs: + kubernetes: circleci/kubernetes@1.0.0 + helm: circleci/helm@2.0.1 + +jobs: + + build-push-docker-image: + docker: + - image: cimg/base:stable + environment: + TAG: << pipeline.git.tag >> + steps: + - checkout + - add_ssh_keys: + fingerprints: + - "4c:8e:00:16:94:44:d9:ad:e9:e9:2c:8b:02:d4:8d:7a" + - run: cp $HOME/.ssh/id_rsa_4c8e00169444d9ade9e92c8b02d48d7a id_rsa + - setup_remote_docker: + version: 20.10.12 + docker_layer_caching: true + - run: docker login rg.fr-par.scw.cloud/lecoffre -u nologin -p $SCW_SECRET_KEY + - run: docker build --tag rg.fr-par.scw.cloud/lecoffre/back:$TAG . + - run: docker push rg.fr-par.scw.cloud/lecoffre/back:$TAG + + + deploy-docker-image: + docker: + - image: cimg/base:stable + environment: + TAG: << pipeline.git.tag >> + steps: + - checkout + - kubernetes/install-kubeconfig: + kubeconfig: KUBECONFIG_DATA + - helm/install-helm-client + - run: + name: Deploy + command: > + helm upgrade + lecoffre-back devops/ -i -f devops/values.yaml + -n lecoffre + --create-namespace + --set lecoffreBack.image.repository='rg.fr-par.scw.cloud/lecoffre/back' + --set lecoffreBack.image.tag=$TAG + + +workflows: + version: 2 + build-and-register: + jobs: + - build-push-docker-image: + filters: + tags: + only: /^v.*/ + branches: + ignore: /.*/ + - deploy-docker-image: + requires: + - build-push-docker-image + context: + - staging + filters: + tags: + only: /^v.*/ + branches: + ignore: /.*/ + diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8a0f8a7d --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +WEB_LABEL=Web +WEB_PORT=3000 +WEB_ROOT_URL=/ + +API_LABEL=Api +API_PORT=3001 +API_ROOT_URL=/api/v1 \ No newline at end of file diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..01e23bcd --- /dev/null +++ b/.env.test @@ -0,0 +1,7 @@ +DATABASE_URL="postgresql://prisma:prisma@localhost:5433/tests" +DATABASE_PORT="5433" +DATABASE_USER="prisma" +DATABASE_PASSWORD="prisma" +DATABASE_NAME="tests" +DATABASE_HOSTNAME="localhost" +DEV_PRISMA_STUDIO_DB_URL="postgresql://prisma:prisma@localhost:5433/tests" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4c9e245b..e505544c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,31 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + + +dist +.next + +# dependencies +/node_modules + +# envs +.env + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + dist dist-* cabal-dev @@ -21,3 +49,4 @@ cabal.project.local cabal.project.local~ .HTF/ .ghc.environment.* +id_rsa diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5937d2ba --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,44 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": false, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[scss]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "terminal.integrated.env.linux": { + "PATH": "${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/vmtools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}", + "VIRTUAL_ENV": "${env:HOME}/elrondsdk/erdpy-venv", + "RUSTUP_HOME": "${env:HOME}/elrondsdk/vendor-rust", + "CARGO_HOME": "${env:HOME}/elrondsdk/vendor-rust" + }, + "terminal.integrated.env.osx": { + "PATH": "${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/vmtools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}", + "VIRTUAL_ENV": "${env:HOME}/elrondsdk/erdpy-venv", + "RUSTUP_HOME": "${env:HOME}/elrondsdk/vendor-rust", + "CARGO_HOME": "${env:HOME}/elrondsdk/vendor-rust" + }, + "terminal.integrated.environmentChangesIndicator": "on", + "terminal.integrated.inheritEnv": true, + "workbench.dialogs.customEnabled": true, + "rust-client.rustupPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rustup", + "rust-client.rlsPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rls", + "rust-client.disableRustup": true, + "rust-client.autoStartRls": false, + "[prisma]": { + "editor.defaultFormatter": "Prisma.prisma" + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..85e1458c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# Install dependencies only when needed +FROM node:19-alpine AS deps + +WORKDIR leCoffre + +RUN npm install -D prisma@4.11.0 +COPY package.json ./ + +RUN apk update && apk add openssh-client git + +COPY id_rsa /root/.ssh/id_rsa +RUN chmod 600 ~/.ssh/id_rsa +RUN eval "$(ssh-agent -s)" && ssh-add /root/.ssh/id_rsa +RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/known_hosts + +RUN npm install --frozen-lockfile + +# Rebuild the source code only when needed +FROM node:19-alpine AS builder + +WORKDIR leCoffre + +COPY --from=deps leCoffre/node_modules ./node_modules +COPY --from=deps leCoffre/package.json package.json +COPY tsconfig.json tsconfig.json +COPY src src + +RUN npx prisma generate +RUN npm run build + +# Production image, copy all the files and run next +FROM node:19-alpine AS production + +WORKDIR leCoffre + +RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser . + +COPY --from=builder --chown=lecoffreuser leCoffre/node_modules ./node_modules +COPY --from=builder --chown=lecoffreuser leCoffre/dist dist +COPY --from=builder --chown=lecoffreuser leCoffre/package.json ./package.json +COPY --from=builder --chown=lecoffreuser leCoffre/src/common/databases ./src/common/databases + +USER lecoffreuser + +CMD ["npm", "run", "api:start"] +EXPOSE 3001 \ No newline at end of file diff --git a/README.md b/README.md index 98dec841..98e7cf4a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,34 @@ # leCoffre [Owner: Elise Hautefaye] + +### A. Docker Launch application +#### 1) Local RSA Key for docker build + +1) Create a file named : `id_rsa` in /src/.ssh +2) Get the RSA Private key on Keeper who is allowed to read the `leCoffre-ressources repo` +3) You can find Key on Keeper inside the folder **LeCoffre project > SSH Key** +4) Copy past in the `id_rsa` that you created step 1 + +:rotating_light: **Be aware to have the id_rsa included in your .gitignore! This ssh shouldn't be push on github** + +> You need to do the same task in the front and back repo. + +#### 2) Build images + +###### a- Back end +`docker build -t "le-coffre-back" -f Dockerfile .` +###### b- Front end +`docker build -t "le-coffre-front" -f Dockerfile .` + +#### 3) Docker Compose + +Docker compose allow to launch multiples images +1) **le-coffre-front** +2) **le-coffre-back** +3) **postgres** + +> Launch your docker container with following command : + +`docker compose up` -> Logs in terminal + +`docker compose up -d` \ No newline at end of file diff --git a/devops/.helmignore b/devops/.helmignore new file mode 100644 index 00000000..691fa13d --- /dev/null +++ b/devops/.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/ \ No newline at end of file diff --git a/devops/Chart.yaml b/devops/Chart.yaml new file mode 100644 index 00000000..208511fb --- /dev/null +++ b/devops/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +name: leCoffre-back +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.0.1 + +# 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: 0.5.6 + diff --git a/devops/templates/docker-pull-secret.yaml b/devops/templates/docker-pull-secret.yaml new file mode 100644 index 00000000..9eb72f38 --- /dev/null +++ b/devops/templates/docker-pull-secret.yaml @@ -0,0 +1,19 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: docker-pull-secret +spec: + refreshInterval: 1h + secretStoreRef: + name: dockerpullsecret-vault-cluster-secret-store + kind: ClusterSecretStore + target: + template: + type: kubernetes.io/dockerconfigjson + name: docker-pull-secret + creationPolicy: Owner + data: + - secretKey: .dockerconfigjson + remoteRef: + key: {{ .Values.dockerPullSecret }} + property: .dockerconfigjson \ No newline at end of file diff --git a/devops/templates/lecoffre-back.yaml b/devops/templates/lecoffre-back.yaml new file mode 100644 index 00000000..f6de560e --- /dev/null +++ b/devops/templates/lecoffre-back.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: lecoffre-back + namespace: {{ .Values.namespace }} +{{if .Values.lecoffreBack.ingress.annotations}} + annotations: +{{toYaml .Values.lecoffreBack.ingress.annotations | indent 4 }} +{{end}} +spec: + tls: + - hosts: {{ .Values.lecoffreBack.ingress.tls.hosts }} + secretName: {{ .Values.lecoffreBack.ingress.tls.secretName }} + rules: + - host: {{ .Values.lecoffreBack.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: lecoffre-back-svc + port: + number: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: lecoffre-back-svc + namespace: {{ .Values.namespace }} + labels: +spec: + ports: + - port: 80 + name: http + targetPort: 3001 + selector: + app: lecoffre-back +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lecoffre-back + namespace: {{ .Values.namespace }} + labels: + app: lecoffre-back +spec: + replicas: 1 + selector: + matchLabels: + app: lecoffre-back + template: + metadata: + annotations: +{{toYaml .Values.lecoffreBack.vault.annotations | indent 8 }} + labels: + app: lecoffre-back + spec: + serviceAccountName: {{ .Values.lecoffreBack.serviceAccountName }} + imagePullSecrets: + - name: docker-pull-secret + containers: + - name: lecoffre-back + image: "{{ .Values.lecoffreBack.image.repository }}:v{{ .Chart.AppVersion }}" +{{if .Values.lecoffreBack.resources}} + resources: +{{toYaml .Values.lecoffreBack.resources | indent 10}} +{{end}} + imagePullPolicy: {{ .Values.lecoffreBack.image.pullPolicy }} + command: [{{ .Values.lecoffreBack.command }}] \ No newline at end of file diff --git a/devops/templates/service-account.yaml b/devops/templates/service-account.yaml new file mode 100644 index 00000000..9a2e2624 --- /dev/null +++ b/devops/templates/service-account.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.lecoffreBack.serviceAccountName }} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.lecoffreBack.serviceAccountName }}-token + annotations: + kubernetes.io/service-account.name: {{ .Values.lecoffreBack.serviceAccountName }} +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/devops/values.yaml b/devops/values.yaml new file mode 100644 index 00000000..a286f980 --- /dev/null +++ b/devops/values.yaml @@ -0,0 +1,44 @@ +dockerPullSecret: secret/data/minteed-stg/config/dockerpullsecret + +namespace: lecoffre + +lecoffreBack: + serviceAccountName: lecoffre-back-sa + command: "'sh', '-c', '. /vault/secrets/envs-api && npm run api:start'" + vault: + role : custom_lecoffre-back_injector_rol + server: https://vault-stg.smart-chain.fr + annotations: + vault.hashicorp.com/agent-pre-populate-only: "true" + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/agent-inject-secret-envs-api: secret/data/lecoffre-back-stg/config/envs-api + vault.hashicorp.com/role: custom_lecoffre-back_injector_rol + vault.hashicorp.com/agent-inject-template-envs-api: | + {{ with secret "secret/data/lecoffre-back-stg/config/envs-api" }} + {{ range $k, $v := .Data.data }} + export {{ $k }}="{{ $v }}" + {{ end }} + {{ end }} + imagePullSecrets: + - name: docker-pull-secret + image: + pullPolicy: Always + repository: "rg.fr-par.scw.cloud/lecoffre/back" + resources: + requests: + cpu: 200m + memory: 1Gi + limits: + memory: 2Gi + ingress: + host: api.stg.lecoffre.smart-chain.fr + tls: + hosts: + - api.stg.lecoffre.smart-chain.fr + secretName: api-tls + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/from-to-www-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 00000000..f5f56f3b --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,12 @@ +version: "3.8" +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-prisma + ports: + - '5433:5432' + environment: + POSTGRES_USER: prisma + POSTGRES_PASSWORD: prisma + POSTGRES_DB: tests \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..800e53af --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,67 @@ +version: "3.8" + +volumes: + db_storage: + +services: + front-end: + image: "le-coffre-front" + ports: + - ${FRONT_PORT}:${FRONT_PORT} + environment: + - FRONT_PORT + - BACK_API_PROTOCOL + - BACK_API_HOST + - BACK_API_PORT + - BACK_API_ROOT_URL + - BACK_API_VERSION + - FRONT_APP_HOST + - FRONT_APP_PORT + - IDNOT_AUTHORIZE_ENDPOINT + - IDNOT_CLIENT_ID + + backend: + image: "le-coffre-back" + ports: + - ${APP_PORT}:${APP_PORT} + environment: + - DATABASE_HOST + - DATABASE_PORT + - DATABASE_USERNAME + - DATABASE_PASSWORD + - DATABASE_NAME + - APP_LABEL + - APP_PORT + - APP_ROOT_URL + - API_ROOT_URL + - DEV_PRISMA_STUDIO_DB_URL + - IDNOT_CONNEXION_URL + - IDNOT_CLIENT_ID + - IDNOT_CLIENT_SECRET + - IDNOT_REDIRECT_URL + + postgres: + image: postgres:11 + restart: always + environment: + - POSTGRES_USER + - POSTGRES_PASSWORD + - POSTGRES_DB + - DATABASE_USERNAME + - DATABASE_PASSWORD + - DATABASE_NAME=${DATABASE_NAME} + ports: + - ${DATABASE_PORT}:5432 + volumes: + - db_storage:/var/lib/postgresql/data + - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh + + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}", + ] + interval: 5s + timeout: 5s + retries: 10 \ No newline at end of file diff --git a/init-data.sh b/init-data.sh new file mode 100755 index 00000000..552a9078 --- /dev/null +++ b/init-data.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e; + +# Make sure this script doesn't encounter any errors while building the docker container for the first time +# Carriage returns might be a problem (the first line #!/bin/bash will create an error) +# On Windows Docker Desktop i had to use the following command (in linux env): +# $ sed -i -e 's/\r$//' init-data.sh + +if [ -n "${DATABASE_USERNAME:-}" ] && [ -n "${DATABASE_PASSWORD:-}" ]; then + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE USER ${DATABASE_USERNAME} WITH PASSWORD '${DATABASE_PASSWORD}'; + ALTER USER ${DATABASE_USERNAME} CREATEDB; + EOSQL +else + echo "SETUP INFO: No Environment variables given!" +fi \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..6784f0af --- /dev/null +++ b/jest.config.js @@ -0,0 +1,19 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + "moduleNameMapper": { + "@Services/(.*)": "/src/services/$1", + "@App/(.*)": "/src/app/$1", + "@Api/(.*)": "/src/app/api/$1", + "@Pages/(.*)": "/src/pages/$1", + "@Common/(.*)": "/src/common/$1", + "@Repositories/(.*)": "/src/common/repositories/$1", + "@Entries/(.*)": "/src/common/entries/$1", + "@Config/(.*)": "/src/common/config/$1", + "@Entities/(.*)": "/src/common/entities/$1", + "@System/(.*)": "/src/common/system/$1", + "@ControllerPattern/(.*)": "/src/common/system/controller-pattern/$1", + "@Test/(.*)": "/src/test/$1" + } +}; \ No newline at end of file diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 00000000..5a4308f5 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["./src/", "./.env", "./package.json", "./tsconfig.json"], + "exec": "npm run start", + "ext": "ts" + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f20818b1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5341 @@ +{ + "name": "lecoffre-back", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lecoffre-back", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@prisma/client": "^4.11.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "classnames": "^2.3.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.24", + "module-alias": "^2.2.2", + "next": "^13.1.5", + "node-cache": "^5.1.2", + "node-schedule": "^2.1.1", + "prisma-query": "^2.0.0", + "reflect-metadata": "^0.1.13", + "ts-node": "^10.9.1", + "tslib": "^2.4.1", + "typedi": "^0.10.0", + "typescript": "^4.9.4", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/cors": "^2.8.13", + "@types/express": "^4.17.16", + "@types/jest": "^29.5.0", + "@types/jsonwebtoken": "^9.0.1", + "@types/node": "^18.11.18", + "@types/node-schedule": "^2.1.0", + "@types/uuid": "^9.0.0", + "dotenv": "^16.0.3", + "jest": "^29.5.0", + "nodemon": "^2.0.20", + "prettier": "2.8.4", + "prisma": "^4.11.0", + "ts-jest": "^29.0.5" + } + }, + "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.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.4", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", + "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/core/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/@babel/core/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/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.4", + "@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.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", + "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.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", + "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/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/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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==", + "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==" + }, + "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/@next/env": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.3.1.tgz", + "integrity": "sha512-EDtCoedIZC7JlUQ3uaQpSc4aVmyhbLHmQVALg7pFfQgOTjgSnn7mKtA0DiCMkYvvsx6aFb5octGMtWrOtGXW9A==" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.3.1.tgz", + "integrity": "sha512-UXPtriEc/pBP8luSLSCZBcbzPeVv+SSjs9cH/KygTbhmACye8/OOXRZO13Z2Wq1G0gLmEAIHQAOuF+vafPd2lw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.3.1.tgz", + "integrity": "sha512-lT36yYxosCfLtplFzJWgo0hrPu6/do8+msgM7oQkPeohDNdhjtjFUgOOwdSnPublLR6Mo2Ym4P/wl5OANuD2bw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.3.1.tgz", + "integrity": "sha512-wRb76nLWJhonH8s3kxC/1tFguEkeOPayIwe9mkaz1G/yeS3OrjeyKMJsb4+Kdg0zbTo53bNCOl59NNtDM7yyyw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.3.1.tgz", + "integrity": "sha512-qz3BzjJRZ16Iq/jrp+pjiYOc0jTjHlfmxQmZk9x/+5uhRP6/eWQSTAPVJ33BMo6oK5O5N4644OgTAbzXzorecg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.3.1.tgz", + "integrity": "sha512-6mgkLmwlyWlomQmpl21I3hxgqE5INoW4owTlcLpNsd1V4wP+J46BlI/5zV5KWWbzjfncIqzXoeGs5Eg+1GHODA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.3.1.tgz", + "integrity": "sha512-uqm5sielhQmKJM+qayIhgZv1KlS5pqTdQ99b+Z7hMWryXS96qE0DftTmMZowBcUL6x7s2vSXyH5wPtO1ON7LBg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.3.1.tgz", + "integrity": "sha512-WomIiTj/v3LevltlibNQKmvrOymNRYL+a0dp5R73IwPWN5FvXWwSELN/kiNALig/+T3luc4qHNTyvMCp9L6U5Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.3.1.tgz", + "integrity": "sha512-M+PoH+0+q658wRUbs285RIaSTYnGBSTdweH/0CdzDgA6Q4rBM0sQs4DHmO3BPP0ltCO/vViIoyG7ks66XmCA5g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.3.1.tgz", + "integrity": "sha512-Sl1F4Vp5Z1rNXWZYqJwMuWRRol4bqOB6+/d7KqkgQ4AcafKPN1PZmpkCoxv4UFHtFNIB7EotnuIhtXu3zScicQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@prisma/client": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.13.0.tgz", + "integrity": "sha512-YaiiICcRB2hatxsbnfB66uWXjcRw3jsZdlAVxmx0cFcTc/Ad/sKdHCcWSnqyDX47vAewkjRFwiLwrOUjswVvmA==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.13.0.tgz", + "integrity": "sha512-HrniowHRZXHuGT9XRgoXEaP2gJLXM5RMoItaY2PkjvuZ+iHc0Zjbm/302MB8YsPdWozAPHHn+jpFEcEn71OgPw==", + "devOptional": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a.tgz", + "integrity": "sha512-fsQlbkhPJf08JOzKoyoD9atdUijuGBekwoOPZC3YOygXEml1MTtgXVpnUNchQlRSY82OQ6pSGQ9PxUe4arcSLQ==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.0.tgz", + "integrity": "sha512-SjY/p4MmECVVEWspzSRpQEM3sjR17sP8PbGxELWrT+YZMBfiUyt1MRUNjMV23zohwlG2HYtCQOsCwsTHguXkyg==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.4.tgz", + "integrity": "sha512-TLG7CsGZZmX9aDF78UuJxnNTfQyRUFU0OYIVyIblr0/wd/HvsIo8wmuB90CszeD2MtLLAE9Tt4cWvk+KVkyGIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "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==", + "dev": true, + "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.34", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz", + "integrity": "sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.1.tgz", + "integrity": "sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", + "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==" + }, + "node_modules/@types/node-schedule": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.0.tgz", + "integrity": "sha512-NiTwl8YN3v/1YCKrDFSmCTkVxFDylueEqsOFdgF+vPsm+AlyJKGAo5yzX1FiOxPsZiN6/r8gJitYx2EaSuBmmg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "dev": true + }, + "node_modules/@types/validator": { + "version": "13.7.15", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.15.tgz", + "integrity": "sha512-yeinDVQunb03AEP8luErFcyf/7Lf7AzKCD0NXfgVoGCCQDNpZET8Jgq74oBgqKld3hafLbfzt/3inUdQvaFeXQ==" + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/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==", + "dev": true, + "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/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001481", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz", + "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.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/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", + "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "dependencies": { + "@types/validator": "^13.7.10", + "libphonenumber-js": "^1.10.14", + "validator": "^13.7.0" + } + }, + "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/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/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/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/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/cron-parser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz", + "integrity": "sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/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/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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/electron-to-chromium": { + "version": "1.4.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.372.tgz", + "integrity": "sha512-MrlFq/j+TYHOjeWsWGYfzevc25HNeJdsF6qaLFrqBTRWZQtWkb1myq/Q2veLWezVaa5OcSZ99CFwTT4aF4Mung==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "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/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/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": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/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/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/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": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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": "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/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": "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-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/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.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/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "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-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "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-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "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-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-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-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.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/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/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/jsonwebtoken/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/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/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/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/le-coffre-resources": { + "resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#0e3dfd0c6e4bfdf2ef78ba13c79ac213c34ab174", + "license": "MIT", + "dependencies": { + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.10.28", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.28.tgz", + "integrity": "sha512-1eAgjLrZA0+2Wgw4hs+4Q/kEBycxQo8ZLYnmOvZ3AlM8ImAVAJgDPlZtISLEzD1vunc2q8s2Pn7XwB7I8U3Kzw==" + }, + "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==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, + "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==", + "peer": true, + "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/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/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/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/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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==", + "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/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": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-13.3.1.tgz", + "integrity": "sha512-eByWRxPzKHs2oQz1yE41LX35umhz86ZSZ+mYyXBqn2IBi2hyUqxBA88avywdr4uyH+hCJczegGsDGWbzQA5Rqw==", + "dependencies": { + "@next/env": "13.3.1", + "@swc/helpers": "0.5.0", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=14.18.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "13.3.1", + "@next/swc-darwin-x64": "13.3.1", + "@next/swc-linux-arm64-gnu": "13.3.1", + "@next/swc-linux-arm64-musl": "13.3.1", + "@next/swc-linux-x64-gnu": "13.3.1", + "@next/swc-linux-x64-musl": "13.3.1", + "@next/swc-win32-arm64-msvc": "13.3.1", + "@next/swc-win32-ia32-msvc": "13.3.1", + "@next/swc-win32-x64-msvc": "13.3.1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "fibers": ">= 3.1.0", + "node-sass": "^6.0.0 || ^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "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/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.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/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prisma": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.13.0.tgz", + "integrity": "sha512-L9mqjnSmvWIRCYJ9mQkwCtj4+JDYYTdhoyo8hlsHNDXaZLh/b4hR0IoKIBbTKxZuyHQzLopb/+0Rvb69uGV7uA==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "4.13.0" + }, + "bin": { + "prisma": "build/index.js", + "prisma2": "build/index.js" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/prisma-query": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prisma-query/-/prisma-query-2.0.0.tgz", + "integrity": "sha512-+5eneJrgTFxW48j4JaWJ8iBwFSH+YQRtA1N+QEzqsREnTEAbs1Bq85xoZP7ZNEXDsoLOoIo4rYfCYRozuVOB9Q==" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/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/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "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/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/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/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.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-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, + "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/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/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==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/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/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "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/ts-jest": { + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", + "integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedi": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", + "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==" + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.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/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/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/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/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/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/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==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "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/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "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/package.json b/package.json new file mode 100644 index 00000000..58892f98 --- /dev/null +++ b/package.json @@ -0,0 +1,81 @@ +{ + "name": "lecoffre-back", + "version": "1.0.0", + "description": "lecoffre project", + "_moduleAliases": { + "@App": "./dist/app", + "@Api": "./dist/app/api", + "@Pages": "./dist/pages", + "@Common": "./dist/common", + "@Services": "./dist/services", + "@Repositories": "./dist/common/repositories", + "@Entries": "./dist/common/entries", + "@Config": "./dist/common/config", + "@Entities": "./dist/common/entities", + "@System": "./dist/common/system", + "@ControllerPattern": "./dist/common/system/controller-pattern", + "@Test": "./dist/test" + }, + "scripts": { + "build-db": "npx prisma migrate dev", + "build": "tsc", + "start": "tsc && node ./dist/entries/App.js", + "dev": "nodemon -V", + "build:test": "tsc && mocha ./dist/entries/Test.js", + "format": "prettier --write src", + "migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy", + "docker:up:test": "docker-compose -f docker-compose-test.yml up -d", + "docker:down:test": "docker-compose down", + "test": "tsc && npm run docker:up:test && npm run migrate:test && dotenv -e .env.test -- jest -i --verbose ./dist/test/* && npm run docker:down:test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/smart-chain-fr/leCoffre-back.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/smart-chain-fr/leCoffre-back/issues" + }, + "homepage": "https://github.com/smart-chain-fr/leCoffre-back#readme", + "dependencies": { + "@prisma/client": "^4.11.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "classnames": "^2.3.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.24", + "module-alias": "^2.2.2", + "next": "^13.1.5", + "node-cache": "^5.1.2", + "node-schedule": "^2.1.1", + "prisma-query": "^2.0.0", + "reflect-metadata": "^0.1.13", + "ts-node": "^10.9.1", + "tslib": "^2.4.1", + "typedi": "^0.10.0", + "typescript": "^4.9.4", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/cors": "^2.8.13", + "@types/express": "^4.17.16", + "@types/jest": "^29.5.0", + "@types/jsonwebtoken": "^9.0.1", + "@types/node": "^18.11.18", + "@types/node-schedule": "^2.1.0", + "@types/uuid": "^9.0.0", + "dotenv": "^16.0.3", + "jest": "^29.5.0", + "nodemon": "^2.0.20", + "prettier": "2.8.4", + "prisma": "^4.11.0", + "ts-jest": "^29.0.5" + }, + "prisma": { + "schema": "src/common/databases/schema.prisma", + "seed": "ts-node src/common/databases/seeders/seeder.ts" + } +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..951346b2 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,17 @@ +module.exports = { + overrides: [ + { + files: ["src/**/*.ts", "src/**/*.tsx", "src/**/*.scss", "./*.js"], + options: { + tabWidth: 4, + useTabs: true, + singleQuote: false, + trailingComma: "all", + printWidth: 140, + semi: true, + bracketSameLine: true, + }, + }, + ], +}; + diff --git a/src/app/HomeController.ts b/src/app/HomeController.ts new file mode 100644 index 00000000..2d84c394 --- /dev/null +++ b/src/app/HomeController.ts @@ -0,0 +1,14 @@ +import { type Response, type Request } from "express"; +import { Controller, Get } from "@ControllerPattern/index"; +import { Service } from "typedi"; +import ApiController from "@Common/system/controller-pattern/ApiController"; + +@Controller() +@Service() +export default class HomeController extends ApiController { + @Get("/") + protected async get(req: Request, res: Response) { + // const query = processFindManyQuery(req.query); + this.httpSuccess(res, "Welcome to the home page!"); + } +} diff --git a/src/app/api/idnot-user/UserInfoController.ts b/src/app/api/idnot-user/UserInfoController.ts new file mode 100644 index 00000000..f3854a1d --- /dev/null +++ b/src/app/api/idnot-user/UserInfoController.ts @@ -0,0 +1,32 @@ +import { Response, Request } from "express"; + import { Controller,Post } from "@ControllerPattern/index"; + import ApiController from "@Common/system/controller-pattern/ApiController"; + import { Service } from "typedi"; +import AuthService from "@Services/private-services/AuthService/AuthService"; +//import User from "le-coffre-resources/dist/Notary"; + + @Controller() + @Service() + export default class UserInfoController extends ApiController { + constructor(private authService: AuthService) { + super(); + } + + /** + * @description Get user created from IdNot authentification + * @returns User + */ + @Post("/api/v1/idnot-user/:code") + protected async getUserInfosFromIdnot(req: Request, response: Response) { + try { + const code = req.params["code"]; + const user = await this.authService.getUserFromIdNotTokens(code!); + //success + this.httpSuccess(response, user); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + +} \ No newline at end of file diff --git a/src/app/api/super-admin/CustomersController.ts b/src/app/api/super-admin/CustomersController.ts new file mode 100644 index 00000000..b43c76e6 --- /dev/null +++ b/src/app/api/super-admin/CustomersController.ts @@ -0,0 +1,133 @@ +import { Response, Request } from "express"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import CustomersService from "@Services/super-admin/CustomersService/CustomersService"; +import { Service } from "typedi"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import { Customer } from "le-coffre-resources/dist/SuperAdmin"; +import { Customers } from "@prisma/client"; +import { validateOrReject } from "class-validator"; + +@Controller() +@Service() +export default class CustomersController extends ApiController { + constructor(private customersService: CustomersService) { + super(); + } + + /** + * @description Get all customers + */ + @Get("/api/v1/super-admin/customers") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + + //call service to get prisma entity + const customersEntity: Customers[] = await this.customersService.get(query); + + //Hydrate ressource with prisma entity + const customers = ObjectHydrate.map(Customer, customersEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, customers); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Create a new customer + */ + @Post("/api/v1/super-admin/customers") + protected async post(req: Request, response: Response) { + try { + //init IUser resource with request body values + const customerEntity = new Customer(); + ObjectHydrate.hydrate(customerEntity, req.body); + + //validate user + await validateOrReject(customerEntity, { groups: ["create"] }); + + //call service to get prisma entity + const prismaEntityCreated = await this.customersService.create(customerEntity); + + //Hydrate ressource with prisma entity + const customerEntityCreated = ObjectHydrate.hydrate(new Customer(), prismaEntityCreated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, customerEntityCreated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Modify a specific customer by uid + */ + @Put("/api/v1/super-admin/customers/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + //init IUser resource with request body values + const customerEntity = new Customer(); + ObjectHydrate.hydrate(customerEntity, req.body); + //validate user + await validateOrReject(customerEntity, { groups: ["update"] }); + + //call service to get prisma entity + const prismaEntityUpdated = await this.customersService.update(uid, customerEntity); + + //Hydrate ressource with prisma entity + const customerEntityUpdated = ObjectHydrate.hydrate(new Customer(), prismaEntityUpdated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, customerEntityUpdated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific customer by uid + */ + @Get("/api/v1/super-admin/customers/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + let customerEntity: Customers; + //get query + if (req.query["q"]) { + const query = JSON.parse(req.query["q"] as string); + customerEntity = await this.customersService.getByUid(uid, query); + } else { + //call service to get prisma entity + customerEntity = await this.customersService.getByUid(uid); + } + + //Hydrate ressource with prisma entity + const customer = ObjectHydrate.hydrate(new Customer(), customerEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, customer); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } +} diff --git a/src/app/api/super-admin/DeedTypesController.ts b/src/app/api/super-admin/DeedTypesController.ts new file mode 100644 index 00000000..ac4bf9c6 --- /dev/null +++ b/src/app/api/super-admin/DeedTypesController.ts @@ -0,0 +1,137 @@ +import { Response, Request } from "express"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import DeedTypesService from "@Services/super-admin/DeedTypesService/DeedTypesService"; +import { DeedTypes } from "@prisma/client"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import { DeedType } from "le-coffre-resources/dist/SuperAdmin"; +import { validateOrReject } from "class-validator"; + +@Controller() +@Service() +export default class DeedTypesController extends ApiController { + constructor(private deedTypesService: DeedTypesService) { + super(); + } + + /** + * @description Get all deedtypes + * @returns Deedtype[] list of deedtypes + */ + @Get("/api/v1/super-admin/deed-types") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + //call service to get prisma entity + const prismaEntity: DeedTypes[] = await this.deedTypesService.get(query); + + //Hydrate ressource with prisma entity + const DeedTypes = ObjectHydrate.map(DeedType, prismaEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, DeedTypes); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Create a new deedtype + * @returns Deedtype created + */ + @Post("/api/v1/super-admin/deed-types") + protected async post(req: Request, response: Response) { + try { + //init DeedType resource with request body values + const deedTypeEntity = new DeedType(); + ObjectHydrate.hydrate(deedTypeEntity, req.body); + + //validate deed type + await validateOrReject(deedTypeEntity, { groups: ["create"] }); + + //call service to get prisma entity + const prismaEntityCreated = await this.deedTypesService.create(deedTypeEntity); + + //Hydrate ressource with prisma entity + const deedTypeEntityCreated = ObjectHydrate.hydrate(new DeedType(), prismaEntityCreated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, deedTypeEntityCreated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Modify a specific deedtype by uid + * @returns Deedtype modified + */ + @Put("/api/v1/super-admin/deed-types/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + //init DeedType resource with request body values + const deedTypeEntity = new DeedType(); + ObjectHydrate.hydrate(deedTypeEntity, req.body); + + //validate deed type + await validateOrReject(deedTypeEntity, { groups: ["update"] }); + + //call service to get prisma entity + const prismaEntityUpdated = await this.deedTypesService.update(uid, deedTypeEntity); + + //Hydrate ressource with prisma entity + const deedTypeEntityUpdated = ObjectHydrate.hydrate(new DeedType(), prismaEntityUpdated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, deedTypeEntityUpdated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific deedtype by uid + * @returns IDeedtype + */ + @Get("/api/v1/super-admin/deed-types/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + let deedTypeEntity: DeedTypes; + //get query + if (req.query["q"]) { + const query = JSON.parse(req.query["q"] as string); + deedTypeEntity = await this.deedTypesService.getByUid(uid, query); + } else { + //call service to get prisma entity + deedTypeEntity = await this.deedTypesService.getByUid(uid); + } + + //Hydrate ressource with prisma entity + const deedType = ObjectHydrate.hydrate(new DeedType(), deedTypeEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, deedType); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } +} diff --git a/src/app/api/super-admin/DeedsController.ts b/src/app/api/super-admin/DeedsController.ts new file mode 100644 index 00000000..df6a836d --- /dev/null +++ b/src/app/api/super-admin/DeedsController.ts @@ -0,0 +1,66 @@ +import { Response, Request } from "express"; +import { Controller, Get } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import DeedsService from "@Services/super-admin/DeedsService/DeedsService"; +import { Service } from "typedi"; +import { Deeds } from "@prisma/client"; +import Deed from "le-coffre-resources/dist/SuperAdmin"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; + +@Controller() +@Service() +export default class DeedsController extends ApiController { + constructor(private deedsService: DeedsService) { + super(); + } + + /** + * @description Get all deeds + * @returns Deed[] list of deeds + */ + @Get("/api/v1/super-admin/deeds") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + //call service to get prisma entity + const prismaEntity: Deeds[] = await this.deedsService.get(query); + + //Hydrate ressource with prisma entity + const deeds = ObjectHydrate.map(Deed, prismaEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, deeds); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific deed by uid + * @returns Deed + */ + @Get("/api/v1/super-admin/deeds/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + //call service to get prisma entity + const deedEntity: Deeds = await this.deedsService.getByUid(uid); + + //Hydrate ressource with prisma entity + const deed = ObjectHydrate.hydrate(new Deed(), deedEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, deed); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + this.httpSuccess(response, await this.deedsService.getByUid("uid")); + } +} diff --git a/src/app/api/super-admin/DocumentTypesController.ts b/src/app/api/super-admin/DocumentTypesController.ts new file mode 100644 index 00000000..e8fe1cc3 --- /dev/null +++ b/src/app/api/super-admin/DocumentTypesController.ts @@ -0,0 +1,133 @@ +import { Response, Request } from "express"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import DocumentTypesService from "@Services/super-admin/DocumentTypesService/DocumentTypesService"; +import { DocumentTypes } from "@prisma/client"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import { DocumentType } from "le-coffre-resources/dist/SuperAdmin"; +import { validateOrReject } from "class-validator"; +//import { validateOrReject } from "class-validator"; + +@Controller() +@Service() +export default class DocumentTypesController extends ApiController { + constructor(private documentTypesService: DocumentTypesService) { + super(); + } + + /** + * @description Get all document-types + */ + @Get("/api/v1/super-admin/document-types") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + + //call service to get prisma entity + const prismaEntity: DocumentTypes[] = await this.documentTypesService.get(query); + + //Hydrate ressource with prisma entity + const documentTypes = ObjectHydrate.map(DocumentType, prismaEntity, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, documentTypes); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Create a new documentType + */ + @Post("/api/v1/super-admin/document-types") + protected async post(req: Request, response: Response) { + try { + //init DocumentType resource with request body values + const documentTypeEntity = new DocumentType(); + ObjectHydrate.hydrate(documentTypeEntity, req.body); + //validate user + await validateOrReject(documentTypeEntity, { groups: ["create"] }); + //call service to get prisma entity + const prismaEntityCreated = await this.documentTypesService.create(documentTypeEntity); + //Hydrate ressource with prisma entity + const userEntityCreated = ObjectHydrate.hydrate(new DocumentType(), prismaEntityCreated, { + strategy: "exposeAll", + }); + //success + this.httpSuccess(response, userEntityCreated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Modify a specific documentType by uid + */ + @Put("/api/v1/super-admin/document-types/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + //init DocumentType resource with request body values + const documentTypeEntity = new DocumentType(); + ObjectHydrate.hydrate(documentTypeEntity, req.body); + + //validate user + await validateOrReject(documentTypeEntity, { groups: ["update"] }); + + //call service to get prisma entity + const prismaEntityUpdated = await this.documentTypesService.update(uid, documentTypeEntity); + + //Hydrate ressource with prisma entity + const documentTypeEntityUpdated = ObjectHydrate.hydrate(new DocumentType(), prismaEntityUpdated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, documentTypeEntityUpdated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific documentType by uid + */ + @Get("/api/v1/super-admin/document-types/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + let documentTypeEntity: DocumentTypes; + //get query + if (req.query["q"]) { + const query = JSON.parse(req.query["q"] as string); + documentTypeEntity = await this.documentTypesService.getByUid(uid, query); + } else { + //call service to get prisma entity + documentTypeEntity = await this.documentTypesService.getByUid(uid); + } + + //Hydrate ressource with prisma entity + const user = ObjectHydrate.hydrate(new DocumentType(), documentTypeEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, user); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } +} diff --git a/src/app/api/super-admin/DocumentsController.ts b/src/app/api/super-admin/DocumentsController.ts new file mode 100644 index 00000000..f1d2c798 --- /dev/null +++ b/src/app/api/super-admin/DocumentsController.ts @@ -0,0 +1,160 @@ +import { Response, Request } from "express"; +import { Controller, Delete, Get, Post, Put } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import DocumentsService from "@Services/super-admin/DocumentsService/DocumentsService"; +import { Documents } from "@prisma/client"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import { Document } from "le-coffre-resources/dist/SuperAdmin"; +import { validateOrReject } from "class-validator"; + +@Controller() +@Service() +export default class DocumentsController extends ApiController { + constructor(private documentsService: DocumentsService) { + super(); + } + + /** + * @description Get all documents + * @returns IDocument[] list of documents + */ + @Get("/api/v1/super-admin/documents") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + + //call service to get prisma entity + const prismaEntity: Documents[] = await this.documentsService.get(query); + + //Hydrate ressource with prisma entity + const documents = ObjectHydrate.map(Document, prismaEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, documents); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Create a new document + * @returns IDocument created + */ + @Post("/api/v1/super-admin/documents") + protected async post(req: Request, response: Response) { + try { + //init Document resource with request body values + const documentEntity = new Document(); + ObjectHydrate.hydrate(documentEntity, req.body); + + //validate document + await validateOrReject(documentEntity, { groups: ["create"] }); + + //call service to get prisma entity + const prismaEntityCreated = await this.documentsService.create(documentEntity); + + //Hydrate ressource with prisma entity + const documentEntityCreated = ObjectHydrate.hydrate(new Document(), prismaEntityCreated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, documentEntityCreated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Update a specific document + */ + @Put("/api/v1/super-admin/documents/:uid") + protected async update(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + //init Document resource with request body values + const documentEntity = new Document(); + ObjectHydrate.hydrate(documentEntity, req.body); + + //validate document + await validateOrReject(documentEntity, { groups: ["create"] }); + + //call service to get prisma entity + const prismaEntityUpdated: Documents = await this.documentsService.update(uid, documentEntity); + + //Hydrate ressource with prisma entity + const document = ObjectHydrate.hydrate(new Document(), prismaEntityUpdated, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, document); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Delete a specific document + */ + @Delete("/api/v1/super-admin/documents/:uid") + protected async delete(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + //call service to get prisma entity + const documentEntity: Documents = await this.documentsService.delete(uid); + + //Hydrate ressource with prisma entity + const document = ObjectHydrate.hydrate(new Document(), documentEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, document); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific document by uid + */ + @Get("/api/v1/super-admin/documents/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + let documentEntity: Documents; + //get query + if (req.query["q"]) { + const query = JSON.parse(req.query["q"] as string); + documentEntity = await this.documentsService.getByUid(uid, query); + } else { + //call service to get prisma entity + documentEntity = await this.documentsService.getByUid(uid); + } + + //Hydrate ressource with prisma entity + const document = ObjectHydrate.hydrate(new Document(), documentEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, document); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } +} diff --git a/src/app/api/super-admin/OfficeFoldersController.ts b/src/app/api/super-admin/OfficeFoldersController.ts new file mode 100644 index 00000000..b123e8cb --- /dev/null +++ b/src/app/api/super-admin/OfficeFoldersController.ts @@ -0,0 +1,132 @@ +import { Response, Request } from "express"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import OfficeFoldersService from "@Services/super-admin/OfficeFoldersService/OfficeFoldersService"; +import { Service } from "typedi"; +import { OfficeFolders } from "@prisma/client"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import { OfficeFolder } from "le-coffre-resources/dist/SuperAdmin"; +import { validateOrReject } from "class-validator"; + +@Controller() +@Service() +export default class OfficeFoldersController extends ApiController { + constructor(private officeFoldersService: OfficeFoldersService) { + super(); + } + + /** + * @description Get all folders + */ + @Get("/api/v1/super-admin/folders") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + //call service to get prisma entity + const prismaEntity: OfficeFolders[] = await this.officeFoldersService.get(query); + //Hydrate ressource with prisma entity + const officeFolders = ObjectHydrate.map(OfficeFolder, prismaEntity, { + strategy: "exposeAll", enableImplicitConversion: true + }); + //success + this.httpSuccess(response, officeFolders); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Create a new folder + */ + @Post("/api/v1/super-admin/folders") + protected async post(req: Request, response: Response) { + try { + //init OfficeFolder resource with request body values + const officeFolderEntity = new OfficeFolder(); + ObjectHydrate.hydrate(officeFolderEntity, req.body); + + //validate folder + await validateOrReject(officeFolderEntity, { groups: ["create"] }); + //call service to get prisma entity + const prismaEntityCreated = await this.officeFoldersService.create(officeFolderEntity); + //Hydrate ressource with prisma entity + const officeFolderEntityCreated = ObjectHydrate.hydrate(new OfficeFolder(), prismaEntityCreated, { + strategy: "exposeAll", enableImplicitConversion: true + }); + //success + this.httpSuccess(response, officeFolderEntityCreated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Modify a specific folder by uid + */ + @Put("/api/v1/super-admin/folders/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + //init IUser resource with request body values + const officeFolderEntity = new OfficeFolder(); + ObjectHydrate.hydrate(officeFolderEntity, req.body); + + //validate user + await validateOrReject(officeFolderEntity, { groups: ["update"] }); + + //call service to get prisma entity + const prismaEntityUpdated = await this.officeFoldersService.update(uid, officeFolderEntity); + + //Hydrate ressource with prisma entity + const officeFolderEntityUpdated = ObjectHydrate.hydrate(new OfficeFolder(), prismaEntityUpdated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, officeFolderEntityUpdated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific folder by uid + * @returns IFolder + */ + @Get("/api/v1/super-admin/folders/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + + let officeFolderEntity: OfficeFolders; + //get query + if (req.query["q"]) { + const query = JSON.parse(req.query["q"] as string); + officeFolderEntity = await this.officeFoldersService.getByUid(uid, query); + } else { + //call service to get prisma entity + officeFolderEntity = await this.officeFoldersService.getByUid(uid); + } + + //Hydrate ressource with prisma entity + const officeFolder = ObjectHydrate.hydrate(new OfficeFolder(), officeFolderEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, officeFolder); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + this.httpSuccess(response, await this.officeFoldersService.getByUid("uid")); + } +} diff --git a/src/app/api/super-admin/OfficesController.ts b/src/app/api/super-admin/OfficesController.ts new file mode 100644 index 00000000..3d0c835e --- /dev/null +++ b/src/app/api/super-admin/OfficesController.ts @@ -0,0 +1,116 @@ +import { Response, Request } from "express"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import OfficesService from "@Services/super-admin/OfficesService/OfficesService"; +import { Service } from "typedi"; +import { Offices } from "@prisma/client"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import { Office as OfficeRessource } from "le-coffre-resources/dist/SuperAdmin"; +import { validateOrReject } from "class-validator"; + +@Controller() +@Service() +export default class OfficesController extends ApiController { + constructor(private officesService: OfficesService) { + super(); + } + /** + * @description Get all offices + */ + @Get("/api/v1/super-admin/offices") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + //call service to get prisma entity + const officesEntity: Offices[] = await this.officesService.get(query); + //Hydrate ressource with prisma entity + const offices = ObjectHydrate.map(OfficeRessource, officesEntity, { strategy: "exposeAll" }); + //success + this.httpSuccess(response, offices); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + /** + * @description Create a new office + */ + @Post("/api/v1/super-admin/offices") + protected async post(req: Request, response: Response) { + try { + //init IUser resource with request body values + const officeEntity = new OfficeRessource(); + ObjectHydrate.hydrate(officeEntity, req.body); + //validate user + await validateOrReject(officeEntity, { groups: ["create"] }); + //call service to get prisma entity + const prismaEntityCreated = await this.officesService.create(officeEntity); + //Hydrate ressource with prisma entity + const officeEntityCreated = ObjectHydrate.hydrate(new OfficeRessource(), prismaEntityCreated, { + strategy: "exposeAll", + }); + //success + this.httpSuccess(response, officeEntityCreated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + /** + * @description Modify a specific office by uid + */ + @Put("/api/v1/super-admin/offices/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + //init IUser resource with request body values + const officeEntity = new OfficeRessource(); + ObjectHydrate.hydrate(officeEntity, req.body); + //validate user + await validateOrReject(officeEntity, { groups: ["update"] }); + //call service to get prisma entity + const prismaEntityUpdated = await this.officesService.update(uid, officeEntity); + //Hydrate ressource with prisma entity + const officeEntityUpdated = ObjectHydrate.hydrate(new OfficeRessource(), prismaEntityUpdated, { + strategy: "exposeAll", + }); + //success + this.httpSuccess(response, officeEntityUpdated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + /** + * @description Get a specific office by uid + */ + @Get("/api/v1/super-admin/offices/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + let officeEntity: Offices; + //get query + if (req.query["q"]) { + const query = JSON.parse(req.query["q"] as string); + officeEntity = await this.officesService.getByUid(uid, query); + } else { + //call service to get prisma entity + officeEntity = await this.officesService.getByUid(uid); + } + //Hydrate ressource with prisma entity + const office = ObjectHydrate.hydrate(new OfficeRessource(), officeEntity, { strategy: "exposeAll" }); + //success + this.httpSuccess(response, office); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } +} diff --git a/src/app/api/super-admin/UsersController.ts b/src/app/api/super-admin/UsersController.ts new file mode 100644 index 00000000..9726c8e1 --- /dev/null +++ b/src/app/api/super-admin/UsersController.ts @@ -0,0 +1,137 @@ +import { Response, Request } from "express"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import UsersService from "@Services/super-admin/UsersService/UsersService"; +import { Service } from "typedi"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import { validateOrReject } from "class-validator"; +import User from "le-coffre-resources/dist/Notary"; +import { Users } from "@prisma/client"; +import { plainToInstance } from "class-transformer"; + +@Controller() +@Service() +export default class UsersController extends ApiController { + constructor(private usersService: UsersService) { + super(); + } + + /** + * @description Get all users + */ + @Get("/api/v1/super-admin/users") + protected async get(req: Request, response: Response) { + try { + //get query + const query = JSON.parse(req.query["q"] as string); + + //call service to get prisma entity + const usersEntity: Users[] = await this.usersService.get(query); + + //Hydrate ressource with prisma entity + const users = plainToInstance(User, usersEntity, { + enableImplicitConversion: true, + excludeExtraneousValues: false, + }); + + //success + this.httpSuccess(response, users); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Create a new user + */ + @Post("/api/v1/super-admin/users") + protected async getAddresses(req: Request, response: Response) { + try { + //init IUser resource with request body values + const userEntity = new User(); + ObjectHydrate.hydrate(userEntity, req.body); + + //validate user + await validateOrReject(userEntity, { groups: ["create"] }); + + //call service to get prisma entity + const prismaEntityCreated = await this.usersService.create(userEntity); + + //Hydrate ressource with prisma entity + const userEntityCreated = ObjectHydrate.hydrate(new User(), prismaEntityCreated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, userEntityCreated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Modify a specific user by uid + */ + @Put("/api/v1/super-admin/users/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + //init IUser resource with request body values + const userEntity = new User(); + ObjectHydrate.hydrate(userEntity, req.body); + + //validate user + await validateOrReject(userEntity, { groups: ["update"] }); + + //call service to get prisma entity + const prismaEntityUpdated = await this.usersService.update(uid, userEntity); + + //Hydrate ressource with prisma entity + const userEntityUpdated = ObjectHydrate.hydrate(new User(), prismaEntityUpdated, { + strategy: "exposeAll", + }); + + //success + this.httpSuccess(response, userEntityUpdated); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } + + /** + * @description Get a specific user by uid + */ + @Get("/api/v1/super-admin/users/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + throw new Error("No uid provided"); + } + let userEntity: Users; + //get query + if (req.query["q"]) { + const query = JSON.parse(req.query["q"] as string); + userEntity = await this.usersService.getByUid(uid, query); + } else { + //call service to get prisma entity + userEntity = await this.usersService.getByUid(uid); + } + + //Hydrate ressource with prisma entity + const user = ObjectHydrate.hydrate(new User(), userEntity, { strategy: "exposeAll" }); + + //success + this.httpSuccess(response, user); + } catch (error) { + this.httpBadRequest(response, error); + return; + } + } +} diff --git a/src/app/index.ts b/src/app/index.ts new file mode 100644 index 00000000..d81c7281 --- /dev/null +++ b/src/app/index.ts @@ -0,0 +1,29 @@ +import { Container } from "typedi"; +import HomeController from "./HomeController"; +import UsersController from "./api/super-admin/UsersController"; +import FoldersController from "./api/super-admin/OfficeFoldersController"; +import CustomersController from "./api/super-admin/CustomersController"; +import OfficesController from "./api/super-admin/OfficesController"; +import DeedsController from "./api/super-admin/DeedsController"; +import DeedTypesController from "./api/super-admin/DeedTypesController"; +import DocumentsController from "./api/super-admin/DocumentsController"; +import DocumentTypesController from "./api/super-admin/DocumentTypesController"; +import IdNotUserInfoController from "./api/idnot-user/UserInfoController"; + +/** + * @description This allow to declare all controllers used in the application + */ +export default { + start: () => { + Container.get(HomeController); + Container.get(UsersController); + Container.get(FoldersController); + Container.get(CustomersController); + Container.get(OfficesController); + Container.get(DeedsController); + Container.get(DeedTypesController); + Container.get(DocumentsController); + Container.get(DocumentTypesController); + Container.get(IdNotUserInfoController); + }, +}; diff --git a/src/app/middlewares/ErrorHandler.ts b/src/app/middlewares/ErrorHandler.ts new file mode 100644 index 00000000..274f5923 --- /dev/null +++ b/src/app/middlewares/ErrorHandler.ts @@ -0,0 +1,24 @@ +import HttpException from "@Common/system/controller-pattern/exceptions/HttpException"; +import HttpCodes from "@Common/system/controller-pattern/HttpCodes"; +import { NextFunction, Request, Response } from "express"; + +export default function errorHandler(error: any, req: Request, response: Response, next: NextFunction) { + const errorStatus: number = error["status"]; + /** + * Used on when try to parse json on request + */ + if (error instanceof SyntaxError && errorStatus === 400 && "body" in error) { + response.status(HttpCodes.BAD_REQUEST).send({ + body: error["body"], + type: error as any["type"], + }); + return; + } + + if (error instanceof HttpException) { + response.status(error.httpCode).send(error.message); + return; + } + + next(error); +} diff --git a/src/common/config/database/IDatabaseConfig.ts b/src/common/config/database/IDatabaseConfig.ts new file mode 100644 index 00000000..2b03000e --- /dev/null +++ b/src/common/config/database/IDatabaseConfig.ts @@ -0,0 +1,4 @@ +export default interface IDatabaseConfig { + name: string; + url?: string; +} diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts new file mode 100644 index 00000000..8624a386 --- /dev/null +++ b/src/common/config/variables/Variables.ts @@ -0,0 +1,68 @@ +import { IsNotEmpty, IsOptional, validateOrReject } from "class-validator"; +import dotenv from "dotenv"; +import { Service } from "typedi"; + +@Service() +export class BackendVariables { + @IsNotEmpty() + public readonly DATABASE_PORT!: string; + + @IsNotEmpty() + public readonly DATABASE_HOST!: string; + + @IsNotEmpty() + public readonly DATABASE_USERNAME!: string; + + @IsNotEmpty() + public readonly DATABASE_PASSWORD!: string; + + @IsNotEmpty() + public readonly DATABASE_NAME!: string; + + @IsNotEmpty() + public readonly API_ROOT_URL!: string; + + @IsOptional() + public readonly APP_LABEL!: string; + + @IsNotEmpty() + public readonly APP_PORT!: string; + + @IsNotEmpty() + public readonly APP_ROOT_URL!: string; + + public readonly NODE_ENV = process.env.NODE_ENV; + + @IsNotEmpty() + public readonly IDNOT_CONNEXION_URL!: string; + + @IsNotEmpty() + public readonly IDNOT_CLIENT_ID!: string; + + @IsNotEmpty() + public readonly IDNOT_CLIENT_SECRET!: string; + + @IsNotEmpty() + public readonly IDNOT_REDIRECT_URL!: string; + + public constructor() { + dotenv.config(); + this.DATABASE_PORT = process.env["DATABASE_PORT"]!; + this.DATABASE_HOST = process.env["DATABASE_HOST"]!; + this.DATABASE_USERNAME = process.env["DATABASE_USERNAME"]!; + this.DATABASE_PASSWORD = process.env["DATABASE_PASSWORD"]!; + this.DATABASE_NAME = process.env["DATABASE_NAME"]!; + this.API_ROOT_URL = process.env["API_ROOT_URL"]!; + this.APP_PORT = process.env["APP_PORT"]!; + this.APP_ROOT_URL = process.env["APP_ROOT_URL"]!; + this.APP_LABEL = process.env["APP_LABEL"]!; + this.IDNOT_CONNEXION_URL = process.env["IDNOT_CONNEXION_URL"]!; + this.IDNOT_CLIENT_ID = process.env["IDNOT_CLIENT_ID"]!; + this.IDNOT_CLIENT_SECRET = process.env["IDNOT_CLIENT_SECRET"]!; + this.IDNOT_REDIRECT_URL = process.env["IDNOT_REDIRECT_URL"]!; + } + public async validate() { + await validateOrReject(this); + return this; + } +} diff --git a/src/common/databases/database.ts b/src/common/databases/database.ts new file mode 100644 index 00000000..9d8b85ac --- /dev/null +++ b/src/common/databases/database.ts @@ -0,0 +1,29 @@ +import { Service } from "typedi"; +import DbProvider from "@Common/system/database"; +import dotenv from "dotenv"; +import { BackendVariables } from "@Common/config/variables/Variables"; + +dotenv.config(); + +@Service() +export default class Database { + protected readonly dbProvider: DbProvider; + constructor(private variables: BackendVariables) { + this.dbProvider = new DbProvider({ + name: this.getDatabaseName(), + }); + } + public getClient() { + return this.dbProvider.getClient(); + } + public async connect() { + await this.dbProvider.connect(); + return this.dbProvider.getClient(); + } + + private getDatabaseName(): string { + const name = this.variables.DATABASE_NAME; + if (!name) throw new Error("Database name is undefined!. Add name of db in the url."); + return name; + } +} diff --git a/src/common/databases/migrations/20230426144239_init/migration.sql b/src/common/databases/migrations/20230426144239_init/migration.sql new file mode 100644 index 00000000..a7c0a49f --- /dev/null +++ b/src/common/databases/migrations/20230426144239_init/migration.sql @@ -0,0 +1,445 @@ +-- CreateEnum +CREATE TYPE "ECivility" AS ENUM ('MALE', 'FEMALE', 'OTHERS'); + +-- CreateEnum +CREATE TYPE "EFolderStatus" AS ENUM ('LIVE', 'ARCHIVED'); + +-- CreateEnum +CREATE TYPE "EOfficeStatus" AS ENUM ('ACTIVATED', 'DESACTIVATED'); + +-- CreateEnum +CREATE TYPE "ENotificationStatus" AS ENUM ('READ', 'UNREAD'); + +-- CreateEnum +CREATE TYPE "ECustomerStatus" AS ENUM ('VALIDATED', 'PENDING', 'ERRONED'); + +-- CreateEnum +CREATE TYPE "EDocumentStatus" AS ENUM ('ASKED', 'DEPOSITED', 'VALIDATED', 'ANCHORED', 'REFUSED'); + +-- CreateTable +CREATE TABLE "addresses" ( + "uid" TEXT NOT NULL, + "address" VARCHAR(255) NOT NULL, + "city" VARCHAR(255) NOT NULL, + "zip_code" INTEGER NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "addresses_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "contacts" ( + "uid" TEXT NOT NULL, + "first_name" VARCHAR(255) NOT NULL, + "last_name" VARCHAR(255) NOT NULL, + "email" VARCHAR(255) NOT NULL, + "phone_number" VARCHAR(50), + "cell_phone_number" VARCHAR(50), + "civility" "ECivility" NOT NULL DEFAULT 'MALE', + "address_uid" VARCHAR(255) NOT NULL, + "birthdate" TIMESTAMP(3), + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "contacts_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "users" ( + "uid" TEXT NOT NULL, + "idNot" VARCHAR(255) NOT NULL, + "contact_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + "office_uid" VARCHAR(255) NOT NULL, + + CONSTRAINT "users_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "offices" ( + "uid" TEXT NOT NULL, + "idNot" VARCHAR(255) NOT NULL, + "name" VARCHAR(255) NOT NULL, + "crpcen" VARCHAR(255) NOT NULL, + "address_uid" VARCHAR(255) NOT NULL, + "office_status" "EOfficeStatus" NOT NULL DEFAULT 'DESACTIVATED', + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "offices_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "customers" ( + "uid" TEXT NOT NULL, + "status" "ECustomerStatus" NOT NULL DEFAULT 'PENDING', + "contact_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "customers_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "user_has_notifications" ( + "uid" TEXT NOT NULL, + "user_uid" VARCHAR(255) NOT NULL, + "notification_uid" VARCHAR(255) NOT NULL, + "notification_status" "ENotificationStatus" NOT NULL DEFAULT 'UNREAD', + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "user_has_notifications_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "notifications" ( + "uid" TEXT NOT NULL, + "message" VARCHAR(255) NOT NULL, + "redirection_url" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "notifications_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "office_folders" ( + "uid" TEXT NOT NULL, + "folder_number" VARCHAR(255) NOT NULL, + "name" VARCHAR(255) NOT NULL, + "description" VARCHAR(255), + "archived_description" VARCHAR(255), + "status" "EFolderStatus" NOT NULL DEFAULT 'LIVE', + "deed_uid" VARCHAR(255) NOT NULL, + "office_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "office_folders_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "office_folder_has_customers" ( + "uid" TEXT NOT NULL, + "customer_uid" VARCHAR(255) NOT NULL, + "office_folder_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "office_folder_has_customers_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "office_folder_has_stakeholder" ( + "uid" TEXT NOT NULL, + "office_folder_uid" VARCHAR(255) NOT NULL, + "user_stakeholder_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "office_folder_has_stakeholder_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "documents" ( + "uid" TEXT NOT NULL, + "document_status" "EDocumentStatus" NOT NULL DEFAULT 'ASKED', + "document_type_uid" VARCHAR(255) NOT NULL, + "blockchain_anchor_uid" VARCHAR(255), + "folder_uid" VARCHAR(255) NOT NULL, + "depositor_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "documents_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "document_history" ( + "uid" TEXT NOT NULL, + "document_status" "EDocumentStatus" NOT NULL DEFAULT 'ASKED', + "refused_reason" VARCHAR(255), + "document_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "document_history_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "files" ( + "uid" TEXT NOT NULL, + "document_uid" VARCHAR(255) NOT NULL, + "file_path" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "files_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "blockchain_anchors" ( + "uid" TEXT NOT NULL, + "smartSigJobId" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "blockchain_anchors_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "document_types" ( + "uid" TEXT NOT NULL, + "name" VARCHAR(255) NOT NULL, + "public_description" VARCHAR(255) NOT NULL, + "private_description" VARCHAR(255), + "office_uid" VARCHAR(255) NOT NULL, + "archived_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "document_types_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "deed_has_document_types" ( + "uid" TEXT NOT NULL, + "document_type_uid" VARCHAR(255) NOT NULL, + "deed_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "deed_has_document_types_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "deed" ( + "uid" TEXT NOT NULL, + "deed_type_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "deed_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "deed_types" ( + "uid" TEXT NOT NULL, + "name" VARCHAR(255) NOT NULL, + "description" VARCHAR(255) NOT NULL, + "archived_at" TIMESTAMP(3), + "office_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "deed_types_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "deed_type_has_document_types" ( + "uid" TEXT NOT NULL, + "document_type_uid" VARCHAR(255) NOT NULL, + "deed_type_uid" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "deed_type_has_document_types_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "addresses_uid_key" ON "addresses"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "contacts_uid_key" ON "contacts"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "contacts_email_key" ON "contacts"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "contacts_cell_phone_number_key" ON "contacts"("cell_phone_number"); + +-- CreateIndex +CREATE UNIQUE INDEX "contacts_address_uid_key" ON "contacts"("address_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_uid_key" ON "users"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_idNot_key" ON "users"("idNot"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_contact_uid_key" ON "users"("contact_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "offices_uid_key" ON "offices"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "offices_idNot_key" ON "offices"("idNot"); + +-- CreateIndex +CREATE UNIQUE INDEX "offices_crpcen_key" ON "offices"("crpcen"); + +-- CreateIndex +CREATE UNIQUE INDEX "offices_address_uid_key" ON "offices"("address_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "customers_uid_key" ON "customers"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "customers_contact_uid_key" ON "customers"("contact_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_has_notifications_uid_key" ON "user_has_notifications"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_has_notifications_notification_uid_user_uid_key" ON "user_has_notifications"("notification_uid", "user_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "notifications_uid_key" ON "notifications"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "office_folders_uid_key" ON "office_folders"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "office_folders_deed_uid_key" ON "office_folders"("deed_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "office_folders_folder_number_office_uid_key" ON "office_folders"("folder_number", "office_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "office_folder_has_customers_uid_key" ON "office_folder_has_customers"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "office_folder_has_customers_office_folder_uid_customer_uid_key" ON "office_folder_has_customers"("office_folder_uid", "customer_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "office_folder_has_stakeholder_uid_key" ON "office_folder_has_stakeholder"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "office_folder_has_stakeholder_office_folder_uid_user_stakeh_key" ON "office_folder_has_stakeholder"("office_folder_uid", "user_stakeholder_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "documents_uid_key" ON "documents"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "document_history_uid_key" ON "document_history"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "files_uid_key" ON "files"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "files_file_path_key" ON "files"("file_path"); + +-- CreateIndex +CREATE UNIQUE INDEX "blockchain_anchors_uid_key" ON "blockchain_anchors"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "blockchain_anchors_smartSigJobId_key" ON "blockchain_anchors"("smartSigJobId"); + +-- CreateIndex +CREATE UNIQUE INDEX "document_types_uid_key" ON "document_types"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "document_types_name_office_uid_key" ON "document_types"("name", "office_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "deed_has_document_types_uid_key" ON "deed_has_document_types"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "deed_has_document_types_deed_uid_document_type_uid_key" ON "deed_has_document_types"("deed_uid", "document_type_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "deed_uid_key" ON "deed"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "deed_types_uid_key" ON "deed_types"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "deed_types_name_office_uid_key" ON "deed_types"("name", "office_uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "deed_type_has_document_types_uid_key" ON "deed_type_has_document_types"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "deed_type_has_document_types_deed_type_uid_document_type_ui_key" ON "deed_type_has_document_types"("deed_type_uid", "document_type_uid"); + +-- AddForeignKey +ALTER TABLE "contacts" ADD CONSTRAINT "contacts_address_uid_fkey" FOREIGN KEY ("address_uid") REFERENCES "addresses"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_contact_uid_fkey" FOREIGN KEY ("contact_uid") REFERENCES "contacts"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "offices" ADD CONSTRAINT "offices_address_uid_fkey" FOREIGN KEY ("address_uid") REFERENCES "addresses"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "customers" ADD CONSTRAINT "customers_contact_uid_fkey" FOREIGN KEY ("contact_uid") REFERENCES "contacts"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_has_notifications" ADD CONSTRAINT "user_has_notifications_user_uid_fkey" FOREIGN KEY ("user_uid") REFERENCES "users"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_has_notifications" ADD CONSTRAINT "user_has_notifications_notification_uid_fkey" FOREIGN KEY ("notification_uid") REFERENCES "notifications"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "office_folders" ADD CONSTRAINT "office_folders_deed_uid_fkey" FOREIGN KEY ("deed_uid") REFERENCES "deed"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "office_folders" ADD CONSTRAINT "office_folders_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "office_folder_has_customers" ADD CONSTRAINT "office_folder_has_customers_customer_uid_fkey" FOREIGN KEY ("customer_uid") REFERENCES "customers"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "office_folder_has_customers" ADD CONSTRAINT "office_folder_has_customers_office_folder_uid_fkey" FOREIGN KEY ("office_folder_uid") REFERENCES "office_folders"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "office_folder_has_stakeholder" ADD CONSTRAINT "office_folder_has_stakeholder_office_folder_uid_fkey" FOREIGN KEY ("office_folder_uid") REFERENCES "office_folders"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "office_folder_has_stakeholder" ADD CONSTRAINT "office_folder_has_stakeholder_user_stakeholder_uid_fkey" FOREIGN KEY ("user_stakeholder_uid") REFERENCES "users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "documents" ADD CONSTRAINT "documents_document_type_uid_fkey" FOREIGN KEY ("document_type_uid") REFERENCES "document_types"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "documents" ADD CONSTRAINT "documents_blockchain_anchor_uid_fkey" FOREIGN KEY ("blockchain_anchor_uid") REFERENCES "blockchain_anchors"("uid") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "documents" ADD CONSTRAINT "documents_folder_uid_fkey" FOREIGN KEY ("folder_uid") REFERENCES "office_folders"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "documents" ADD CONSTRAINT "documents_depositor_uid_fkey" FOREIGN KEY ("depositor_uid") REFERENCES "customers"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "document_history" ADD CONSTRAINT "document_history_document_uid_fkey" FOREIGN KEY ("document_uid") REFERENCES "documents"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "files" ADD CONSTRAINT "files_document_uid_fkey" FOREIGN KEY ("document_uid") REFERENCES "documents"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "document_types" ADD CONSTRAINT "document_types_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "deed_has_document_types" ADD CONSTRAINT "deed_has_document_types_document_type_uid_fkey" FOREIGN KEY ("document_type_uid") REFERENCES "document_types"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "deed_has_document_types" ADD CONSTRAINT "deed_has_document_types_deed_uid_fkey" FOREIGN KEY ("deed_uid") REFERENCES "deed"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "deed" ADD CONSTRAINT "deed_deed_type_uid_fkey" FOREIGN KEY ("deed_type_uid") REFERENCES "deed_types"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "deed_types" ADD CONSTRAINT "deed_types_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "deed_type_has_document_types" ADD CONSTRAINT "deed_type_has_document_types_document_type_uid_fkey" FOREIGN KEY ("document_type_uid") REFERENCES "document_types"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "deed_type_has_document_types" ADD CONSTRAINT "deed_type_has_document_types_deed_type_uid_fkey" FOREIGN KEY ("deed_type_uid") REFERENCES "deed_types"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/migrations/migration_lock.toml b/src/common/databases/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/src/common/databases/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma new file mode 100644 index 00000000..e0293f41 --- /dev/null +++ b/src/common/databases/schema.prisma @@ -0,0 +1,327 @@ +// This the Prisma schema file + +generator client { + provider = "prisma-client-js" + //binaryTargets = ["native"] +} + +datasource db { + provider = "postgresql" + url = env("DEV_PRISMA_STUDIO_DB_URL") +} + +// Entités -> snake_case +// table -> CamelCase + +// Models au sein de la Database (Prisma) +// Permet d'utiliser du cable_case dans les tables et du SnakeCase dans les models +// id String @unique @default(auto()) @map("_id") @db.ObjectId // @map de la table checker le naming avec le tiret + +model Addresses { + uid String @id @unique @default(uuid()) + address String @db.VarChar(255) + city String @db.VarChar(255) + zip_code Int + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + contacts Contacts? + office Offices? + + @@map("addresses") +} + +model Contacts { + uid String @id @unique @default(uuid()) + first_name String @db.VarChar(255) + last_name String @db.VarChar(255) + email String @unique @db.VarChar(255) + phone_number String? @db.VarChar(50) + cell_phone_number String? @unique @db.VarChar(50) + civility ECivility @default(MALE) + address Addresses? @relation(fields: [address_uid], references: [uid], onDelete: Cascade) + address_uid String @unique @db.VarChar(255) + birthdate DateTime? + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + users Users? + customers Customers? + + @@map("contacts") +} + +model Users { + uid String @id @unique @default(uuid()) @map("uid") + idNot String @unique @db.VarChar(255) + contact Contacts @relation(fields: [contact_uid], references: [uid], onDelete: Cascade) + contact_uid String @unique @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + office_membership Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) + office_uid String @db.VarChar(255) + user_has_notifications UserHasNotifications[] + office_folder_has_stakeholder OfficeFolderHasStakeholders[] + + @@map("users") +} + +model Offices { + uid String @id @unique @default(uuid()) + idNot String @unique @db.VarChar(255) + name String @db.VarChar(255) + crpcen String @unique @db.VarChar(255) + address Addresses @relation(fields: [address_uid], references: [uid], onDelete: Cascade) + address_uid String @unique @db.VarChar(255) + office_status EOfficeStatus @default(DESACTIVATED) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + deed_types DeedTypes[] + users Users[] + office_folders OfficeFolders[] + document_types DocumentTypes[] + + @@map("offices") +} + +model Customers { + uid String @id @unique @default(uuid()) + status ECustomerStatus @default(PENDING) + contact Contacts @relation(fields: [contact_uid], references: [uid], onDelete: Cascade) + contact_uid String @unique @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + office_folder_has_customers OfficeFolderHasCustomers[] + documents Documents[] + + @@map("customers") +} + +model UserHasNotifications { + uid String @id @unique @default(uuid()) + user Users @relation(fields: [user_uid], references: [uid]) + user_uid String @db.VarChar(255) + notification Notifications @relation(fields: [notification_uid], references: [uid], onDelete: Cascade) + notification_uid String @db.VarChar(255) + notification_status ENotificationStatus @default(UNREAD) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@unique([notification_uid, user_uid]) + @@map("user_has_notifications") +} + +model Notifications { + uid String @id @unique @default(uuid()) + message String @db.VarChar(255) + redirection_url String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + user_has_notifications UserHasNotifications[] + + @@map("notifications") +} + +model OfficeFolders { + uid String @id @unique @default(uuid()) + folder_number String @db.VarChar(255) + name String @db.VarChar(255) + description String? @db.VarChar(255) + archived_description String? @db.VarChar(255) + status EFolderStatus @default(LIVE) + deed Deeds @relation(fields: [deed_uid], references: [uid], onDelete: Cascade) + deed_uid String @unique @db.VarChar(255) + office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) + office_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + office_folder_has_customers OfficeFolderHasCustomers[] + office_folder_has_stakeholder OfficeFolderHasStakeholders[] + documents Documents[] + + @@unique([folder_number, office_uid]) + @@map("office_folders") +} + +model OfficeFolderHasCustomers { + uid String @id @unique @default(uuid()) + customer Customers @relation(fields: [customer_uid], references: [uid], onDelete: Cascade) + customer_uid String @db.VarChar(255) + office_folder OfficeFolders @relation(fields: [office_folder_uid], references: [uid], onDelete: Cascade) + office_folder_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@unique([office_folder_uid, customer_uid]) + @@map("office_folder_has_customers") +} + +model OfficeFolderHasStakeholders { + uid String @id @unique @default(uuid()) + office_folder OfficeFolders @relation(fields: [office_folder_uid], references: [uid], onDelete: Cascade) + office_folder_uid String @db.VarChar(255) + user_stakeholder Users @relation(fields: [user_stakeholder_uid], references: [uid], onDelete: Cascade) + user_stakeholder_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@unique([office_folder_uid, user_stakeholder_uid]) + @@map("office_folder_has_stakeholder") +} + +model Documents { + uid String @id @unique @default(uuid()) + document_status EDocumentStatus @default(ASKED) + document_type DocumentTypes @relation(fields: [document_type_uid], references: [uid]) + document_type_uid String @db.VarChar(255) + blockchain_anchor BlockchainAnchors? @relation(fields: [blockchain_anchor_uid], references: [uid]) + blockchain_anchor_uid String? @db.VarChar(255) + folder OfficeFolders @relation(fields: [folder_uid], references: [uid]) + folder_uid String @db.VarChar(255) + depositor Customers @relation(fields: [depositor_uid], references: [uid], onDelete: Cascade) + depositor_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + files Files[] + document_history DocumentHistory[] + + @@map("documents") +} + +model DocumentHistory { + uid String @id @unique @default(uuid()) + document_status EDocumentStatus @default(ASKED) + refused_reason String? @db.VarChar(255) + document Documents @relation(fields: [document_uid], references: [uid], onDelete: Cascade) + document_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@map("document_history") +} + +model Files { + uid String @id @unique @default(uuid()) + document Documents @relation(fields: [document_uid], references: [uid], onDelete: Cascade) + document_uid String @db.VarChar(255) + file_path String @unique @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@map("files") +} + +model BlockchainAnchors { + uid String @id @unique @default(uuid()) + smartSigJobId String @unique @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + documents Documents[] + + @@map("blockchain_anchors") +} + +model DocumentTypes { + uid String @id @unique @default(uuid()) + name String @db.VarChar(255) + public_description String @db.VarChar(255) + private_description String? @db.VarChar(255) + office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) + office_uid String @db.VarChar(255) + archived_at DateTime? + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + documents Documents[] + deed_has_document_types DeedHasDocumentTypes[] + deed_type_has_document_types DeedTypeHasDocumentTypes[] + + @@unique([name, office_uid]) + @@map("document_types") +} + +model DeedHasDocumentTypes { + uid String @id @unique @default(uuid()) + document_type DocumentTypes @relation(fields: [document_type_uid], references: [uid], onDelete: Cascade) + document_type_uid String @db.VarChar(255) + deed Deeds @relation(fields: [deed_uid], references: [uid], onDelete: Cascade) + deed_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@unique([deed_uid, document_type_uid]) + @@map("deed_has_document_types") +} + +model Deeds { + uid String @id @unique @default(uuid()) + deed_type DeedTypes @relation(fields: [deed_type_uid], references: [uid], onDelete: Cascade) + deed_type_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + deed_has_document_types DeedHasDocumentTypes[] + office_folder OfficeFolders? + + @@map("deed") +} + +model DeedTypes { + uid String @id @unique @default(uuid()) + name String @db.VarChar(255) + description String @db.VarChar(255) + archived_at DateTime? + office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) + office_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + deed Deeds[] + deed_type_has_document_types DeedTypeHasDocumentTypes[] + + @@unique([name, office_uid]) + @@map("deed_types") +} + +model DeedTypeHasDocumentTypes { + uid String @id @unique @default(uuid()) + document_type DocumentTypes @relation(fields: [document_type_uid], references: [uid], onDelete: Cascade) + document_type_uid String @db.VarChar(255) + deed_type DeedTypes @relation(fields: [deed_type_uid], references: [uid], onDelete: Cascade) + deed_type_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + + @@unique([deed_type_uid, document_type_uid]) + @@map("deed_type_has_document_types") +} + +enum ECivility { + MALE + FEMALE + OTHERS +} + +enum EFolderStatus { + LIVE + ARCHIVED +} + +enum EOfficeStatus { + ACTIVATED + DESACTIVATED +} + +enum ENotificationStatus { + READ + UNREAD +} + +enum ECustomerStatus { + VALIDATED + PENDING + ERRONED +} + +enum EDocumentStatus { + ASKED + DEPOSITED + VALIDATED + ANCHORED + REFUSED +} diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts new file mode 100644 index 00000000..d76ede13 --- /dev/null +++ b/src/common/databases/seeders/seeder.ts @@ -0,0 +1,512 @@ +import { + Addresses, + Contacts, + Customers, + DeedHasDocumentTypes, + DeedTypeHasDocumentTypes, + DeedTypes, + Deeds, + DocumentHistory, + DocumentTypes, + Documents, + EDocumentStatus, + EFolderStatus, + EOfficeStatus, + Files, + OfficeFolderHasCustomers, + OfficeFolders, + Offices, + Users, + ECivility, + ECustomerStatus, + PrismaClient +} from "@prisma/client"; + +(async () => { + + const prisma = new PrismaClient(); + + const existingData = await prisma.contacts.findFirst({ where: { email: "john.doe@example.com" } }); + if (existingData) { + console.log('Seed data already exists. Skipping seeding process.'); + return; + } + + const randomString = () => { + const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let result = ""; + for (let i = 10; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; + return result; + }; + const uidCustomer1: string = randomString(); + const uidCustomer2: string = randomString(); + + const uidContact1: string = randomString(); + const uidContact2: string = randomString(); + + const uidAddress1: string = randomString(); + const uidAddress2: string = randomString(); + + const uidOffice1: string = randomString(); + const uidOffice2: string = randomString(); + + const uidUser1: string = randomString(); + const uidUser2: string = randomString(); + + const uidOfficeFolder1: string = randomString(); + const uidOfficeFolder2: string = randomString(); + const uidOfficeFolder3: string = randomString(); + const uidOfficeFolder4: string = randomString(); + const uidOfficeFolder5: string = randomString(); + + const uidDeed1: string = randomString(); + const uidDeed2: string = randomString(); + const uidDeed3: string = randomString(); + const uidDeed4: string = randomString(); + const uidDeed5: string = randomString(); + + const uidDeedType1: string = randomString(); + const uidDeedType2: string = randomString(); + + const uidDocument1: string = randomString(); + const uidDocument2: string = randomString(); + + const uidDocumentType1: string = randomString(); + const uidDocumentType2: string = randomString(); + + const uidOfficeFolderHasCustomer1: string = randomString(); + const uidOfficeFolderHasCustomer2: string = randomString(); + + const uidFiles1: string = randomString(); + const uidFiles2: string = randomString(); + + const uidDeedHasDocumentType1: string = randomString(); + const uidDeedHasDocumentType2: string = randomString(); + + const uidDeedTypeHasDocumentType1: string = randomString(); + const uidDeedTypeHasDocumentType2: string = randomString(); + + const uidDocumentHistory1: string = randomString(); + const uidDocumentHistory2: string = randomString(); + + const customers: Customers[] = [ + { + uid: uidCustomer1, + contact_uid: uidContact1, + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + uid: uidCustomer2, + contact_uid: uidContact2, + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + ]; + + const addresses: Addresses[] = [ + { + uid: uidAddress1, + address: "123 Main St", + city: "Los Angeles", + zip_code: 90001, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidAddress2, + address: "Rue Pierre Emillion", + city: "Paris", + zip_code: 75003, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const contacts: Contacts[] = [ + { + uid: uidContact1, + address_uid: uidAddress1, + first_name: "John", + last_name: "Doe", + email: "john.doe@example.com", + phone_number: randomString(), + cell_phone_number: randomString(), + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + uid: uidContact2, + address_uid: uidAddress2, + first_name: "Jane", + last_name: "Doe", + email: "jane.doe@example.com", + phone_number: randomString(), + cell_phone_number: randomString(), + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + ]; + + const offices: Offices[] = [ + { + uid: uidOffice1, + idNot: randomString(), + name: "LA Office", + crpcen: randomString(), + address_uid: uidAddress1, + created_at: new Date(), + updated_at: new Date(), + office_status: EOfficeStatus.ACTIVATED, + }, + { + uid: uidOffice2, + idNot: randomString(), + name: "NYC Office", + crpcen: randomString(), + address_uid: uidAddress2, + created_at: new Date(), + updated_at: new Date(), + office_status: EOfficeStatus.DESACTIVATED, + }, + ]; + + const users: Users[] = [ + { + uid: uidUser1, + created_at: new Date(), + updated_at: new Date(), + idNot: randomString(), + contact_uid: uidContact1, + office_uid: uidOffice1, + }, + { + uid: uidUser2, + created_at: new Date(), + updated_at: new Date(), + idNot: randomString(), + contact_uid: uidContact2, + office_uid: uidOffice2, + }, + ]; + + const officeFolders: OfficeFolders[] = [ + { + uid: uidOfficeFolder1, + folder_number: "0001", + name: "Dossier", + deed_uid: uidDeed1, + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office_uid: uidOffice1, + description: null, + archived_description: null, + }, + { + uid: uidOfficeFolder2, + folder_number: "0002", + name: "Dossier", + deed_uid: uidDeed2, + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office_uid: uidOffice2, + description: null, + archived_description: null, + }, + { + uid: uidOfficeFolder3, + folder_number: "0003", + name: "Dossier", + deed_uid: uidDeed3, + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office_uid: uidOffice2, + description: null, + archived_description: null, + }, + { + uid: uidOfficeFolder4, + folder_number: "0004", + name: "Dossier", + deed_uid: uidDeed4, + status: EFolderStatus.ARCHIVED, + created_at: new Date(), + updated_at: new Date(), + office_uid: uidOffice2, + description: null, + archived_description: null, + }, + { + uid: uidOfficeFolder5, + folder_number: "0005", + name: "Dossier", + deed_uid: uidDeed5, + status: EFolderStatus.ARCHIVED, + created_at: new Date(), + updated_at: new Date(), + office_uid: uidOffice2, + description: null, + archived_description: null, + } + ]; + + const deeds: Deeds[] = [ + { + uid: uidDeed1, + deed_type_uid: uidDeedType1, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDeed2, + deed_type_uid: uidDeedType2, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDeed3, + deed_type_uid: uidDeedType2, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDeed4, + deed_type_uid: uidDeedType2, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDeed5, + deed_type_uid: uidDeedType2, + created_at: new Date(), + updated_at: new Date(), + } + ]; + + const deedTypes: DeedTypes[] = [ + { + uid: uidDeedType1, + name: "Acte de mariage", + archived_at: null, + description: "Acte regroupant deux personnes en mariage", + office_uid: uidOffice1, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDeedType2, + name: "Vente d'un bien immobilier", + archived_at: null, + description: "Permet de vendre un bien immobilier à une entité ou une personne physique", + office_uid: uidOffice2, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const documents: Documents[] = [ + { + uid: uidDocument1, + blockchain_anchor_uid: null, + depositor_uid: uidCustomer1, + document_status: EDocumentStatus.ASKED, + folder_uid: uidOfficeFolder1, + document_type_uid: uidDocumentType1, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDocument2, + blockchain_anchor_uid: null, + depositor_uid: uidCustomer2, + document_status: EDocumentStatus.ASKED, + folder_uid: uidOfficeFolder2, + document_type_uid: uidDocumentType2, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const documentTypes: DocumentTypes[] = [ + { + uid: uidDocumentType1, + archived_at: null, + name: "Acte de naissance", + office_uid: uidOffice1, + private_description: "Ce document est confidentiel, et ne doit pas être divulgué", + public_description: "Acte de naissance est un document officiel qui atteste de la naissance d'une personne", + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDocumentType2, + archived_at: null, + name: "Carte d'identité", + office_uid: uidOffice2, + private_description: "Ce document est confidentiel, demander un recto-verso au client", + public_description: "Carte d'identité est un document officiel qui atteste de l'identité d'une personne", + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const officeFolderHasCustomers: OfficeFolderHasCustomers[] = [ + { + uid: uidOfficeFolderHasCustomer1, + customer_uid: uidCustomer1, + office_folder_uid: uidOfficeFolder1, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidOfficeFolderHasCustomer2, + customer_uid: uidCustomer2, + office_folder_uid: uidOfficeFolder2, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const files: Files[] = [ + { + uid: uidFiles1, + document_uid: uidDocument1, + file_path: "https://www.google1.com", + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidFiles2, + document_uid: uidDocument2, + file_path: "https://www.google2.com", + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const deedHasDocumentTypes: DeedHasDocumentTypes[] = [ + { + uid: uidDeedHasDocumentType1, + deed_uid: uidDeed1, + document_type_uid: uidDocumentType1, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDeedHasDocumentType2, + deed_uid: uidDeed2, + document_type_uid: uidDocumentType2, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const deedTypeHasDocumentTypes: DeedTypeHasDocumentTypes[] = [ + { + uid: uidDeedTypeHasDocumentType1, + deed_type_uid: uidDeedType1, + document_type_uid: uidDocumentType1, + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDeedTypeHasDocumentType2, + deed_type_uid: uidDeedType2, + document_type_uid: uidDocumentType2, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const documentHistories: DocumentHistory[] = [ + { + uid: uidDocumentHistory1, + document_status: EDocumentStatus.ASKED, + document_uid: uidDocument1, + refused_reason: "Le document n'est pas conforme", + created_at: new Date(), + updated_at: new Date(), + }, + { + uid: uidDocumentHistory2, + document_status: EDocumentStatus.DEPOSITED, + document_uid: uidDocument1, + refused_reason: "Le document n'est pas conforme", + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + + for (const address of addresses) { + await prisma.addresses.create({data: address}); + } + + for (const contact of contacts) { + await prisma.contacts.create({ data: contact }); + } + + for (const office of offices) { + await prisma.offices.create({ data: office }); + } + + for (const user of users) { + await prisma.users.create({ data: user }); + } + + + for (const customer of customers) { + await prisma.customers.create({ data: customer }); + } + + for (const deedType of deedTypes) { + await prisma.deedTypes.create({ data: deedType }); + } + + for (const deed of deeds) { + await prisma.deeds.create({ data: deed }); + + } + for (const officeFolder of officeFolders) { + await prisma.officeFolders.create({ data: officeFolder }); + } + + for (const documentType of documentTypes) { + await prisma.documentTypes.create({ data: documentType }); + } + + for (const document of documents) { + await prisma.documents.create({ data: document }); + } + + for (const file of files) { + await prisma.files.create({ data: file }); + } + + for (const documentHistory of documentHistories) { + await prisma.documentHistory.create({ data: documentHistory }); + } + + for (const officeFolderHasCustomer of officeFolderHasCustomers) { + await prisma.officeFolderHasCustomers.create({ data: officeFolderHasCustomer }); + } + + for (const deedHasDocumentType of deedHasDocumentTypes) { + await prisma.deedHasDocumentTypes.create({ data: deedHasDocumentType }); + } + + for (const deedTypeHasDocumentType of deedTypeHasDocumentTypes) { + await prisma.deedTypeHasDocumentTypes.create({ data: deedTypeHasDocumentType }); + } + + console.log(">MOCK DATA - Seeding completed!"); +})(); \ No newline at end of file diff --git a/src/common/helpers/ObjectHydrate.ts b/src/common/helpers/ObjectHydrate.ts new file mode 100644 index 00000000..199b8733 --- /dev/null +++ b/src/common/helpers/ObjectHydrate.ts @@ -0,0 +1,13 @@ +import { type ClassTransformOptions, plainToClassFromExist, plainToInstance } from "class-transformer"; + +export default abstract class ObjectHydrate { + public static hydrate(object: T, from: Partial, options?: ClassTransformOptions): T { + return plainToClassFromExist(object, from, options); + } + + public static map(ClassEntity: { new (): T }, fromArray: Partial[], options?: ClassTransformOptions): T[] { + return fromArray.map((from) => { + return plainToInstance(ClassEntity, from, options); + }); + } +} diff --git a/src/common/repositories/AddressesRepository.ts b/src/common/repositories/AddressesRepository.ts new file mode 100644 index 00000000..7bd53e84 --- /dev/null +++ b/src/common/repositories/AddressesRepository.ts @@ -0,0 +1,42 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Addresses } from "@prisma/client"; + +@Service() +export default class AddressesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().addresses; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many addresses + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Find one address + */ + public async findOneByUid(uid: string): Promise { + const addressEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!addressEntity) { + throw new Error("Address not found"); + } + + return addressEntity; + } +} diff --git a/src/common/repositories/BaseRepository.ts b/src/common/repositories/BaseRepository.ts new file mode 100644 index 00000000..45e050b6 --- /dev/null +++ b/src/common/repositories/BaseRepository.ts @@ -0,0 +1,4 @@ +export default abstract class BaseRepository { + protected readonly maxFetchRows = 100; + protected readonly defaultFetchRows = 50; +} diff --git a/src/common/repositories/ContactsRepository.ts b/src/common/repositories/ContactsRepository.ts new file mode 100644 index 00000000..b302d1f6 --- /dev/null +++ b/src/common/repositories/ContactsRepository.ts @@ -0,0 +1,42 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Contacts } from "@prisma/client"; + +@Service() +export default class ContactsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().contacts; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many contacts + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Find unique contact + */ + public async findOneByUid(uid: string): Promise { + const contactEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!contactEntity) { + throw new Error("contact not found"); + } + + return contactEntity; + } +} diff --git a/src/common/repositories/CustomersRepository.ts b/src/common/repositories/CustomersRepository.ts new file mode 100644 index 00000000..b08bc0d5 --- /dev/null +++ b/src/common/repositories/CustomersRepository.ts @@ -0,0 +1,111 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Customers, ECivility, ECustomerStatus, Prisma } from "@prisma/client"; +import { Customer } from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class CustomersRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().customers; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many customers + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a customer + */ + public async create(customer: Customer): Promise { + const createArgs: Prisma.CustomersCreateArgs = { + data: { + status: ECustomerStatus.PENDING, + contact: { + create: { + first_name: customer.contact.first_name, + last_name: customer.contact.last_name, + email: customer.contact.email, + phone_number: customer.contact.phone_number, + cell_phone_number: customer.contact?.cell_phone_number, + civility: ECivility[customer.contact.civility as keyof typeof ECivility], + address: {} + }, + }, + }, + }; + + if (customer.contact.address) { + createArgs.data.contact!.create!.address!.create = { + address: customer.contact.address!.address, + zip_code: customer.contact.address!.zip_code, + city: customer.contact.address!.city, + }; + } + return this.model.create(createArgs); + } + + /** + * @description : Update data from a customer + */ + public async update(uid: string, customer: Customer): Promise { + const updateArgs: Prisma.CustomersUpdateArgs = { + where: { + uid: uid, + }, + data: { + status: ECustomerStatus[customer.status as keyof typeof ECustomerStatus], + contact: { + update: { + first_name: customer.contact.first_name, + last_name: customer.contact.last_name, + email: customer.contact.email, + phone_number: customer.contact.phone_number, + cell_phone_number: customer.contact.cell_phone_number, + civility: ECivility[customer.contact.civility as keyof typeof ECivility], + address: {} + }, + }, + }, + } + if (customer.contact.address) { + updateArgs.data.contact!.update!.address!.update = { + address: customer.contact.address!.address, + zip_code: customer.contact.address!.zip_code, + city: customer.contact.address!.city, + }; + } + return this.model.update(updateArgs); + } + + /** + * @description : Find unique customer + */ + public async findOneByUid(uid: string, query?: any): Promise { + const findOneArgs: Prisma.CustomersFindUniqueArgs = { + where: { + uid: uid, + } + }; + if(query) { + findOneArgs.include = query + } + const customerEntity = await this.model.findUnique(findOneArgs); + + if (!customerEntity) { + throw new Error("Customer not found"); + } + + return customerEntity; + } +} diff --git a/src/common/repositories/DeedTypesHasDocumentTypesRepository.ts b/src/common/repositories/DeedTypesHasDocumentTypesRepository.ts new file mode 100644 index 00000000..53cb40a7 --- /dev/null +++ b/src/common/repositories/DeedTypesHasDocumentTypesRepository.ts @@ -0,0 +1,42 @@ +import Database from "@Common/databases/database"; +import { DeedTypeHasDocumentTypes } from "@prisma/client"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; + +@Service() +export default class DeedTypeHasDocumentTypesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().deedTypeHasDocumentTypes; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many relations between deed type and a document type + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Find unique relation between deed type and a document type + */ + public async findOneByUid(uid: string): Promise { + const deedTypeHasDoculmentTypesEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!deedTypeHasDoculmentTypesEntity) { + throw new Error("deed type not found"); + } + + return deedTypeHasDoculmentTypesEntity; + } +} diff --git a/src/common/repositories/DeedTypesRepository.ts b/src/common/repositories/DeedTypesRepository.ts new file mode 100644 index 00000000..47ea54c1 --- /dev/null +++ b/src/common/repositories/DeedTypesRepository.ts @@ -0,0 +1,111 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { DeedTypes, Prisma } from "@prisma/client"; +import { DeedType } from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class DeedTypesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().deedTypes; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many deed types + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create new deed type + */ + public async create(deedType: DeedType): Promise { + const createArgs: Prisma.DeedTypesCreateArgs = { + data: { + name: deedType.name, + description: deedType.description, + office: { + connect: { + uid: deedType.office.uid, + }, + }, + } + }; + if (deedType.deed_type_has_document_types) { + createArgs.data.deed_type_has_document_types = { + createMany: { + data: deedType.deed_type_has_document_types.map((relation) => ({ + document_type_uid: relation.document_type.uid!, + })), + skipDuplicates: true, + }, + }; + } + return this.model.create(createArgs); + } + + /** + * @description : Update data of a deed type + */ + public async update(uid: string, deedType: DeedType): Promise { + const updateArgs: Prisma.DeedTypesUpdateArgs = { + where: { + uid: uid, + }, + data: { + name: deedType.name, + description: deedType.description, + archived_at: deedType.archived_at, + office: { + connect: { + uid: deedType.office.uid, + }, + }, + }, + include: { + deed_type_has_document_types: true, + }, + }; + if (deedType.deed_type_has_document_types) { + updateArgs.data.deed_type_has_document_types = { + deleteMany: { deed_type_uid: uid }, + createMany: { + data: deedType.deed_type_has_document_types.map((relation) => ({ + document_type_uid: relation.document_type.uid!, + })), + skipDuplicates: true, + }, + }; + } + return this.model.update(updateArgs); + } + + /** + * @description : Find unique deed type + */ + public async findOneByUid(uid: string, query?: any): Promise { + const findOneArgs: Prisma.DeedTypesFindUniqueArgs = { + where: { + uid: uid, + } + }; + if(query) { + findOneArgs.include = query + } + const deedTypeEntity = await this.model.findUnique(findOneArgs); + + if (!deedTypeEntity) { + throw new Error("deed type not found"); + } + + return deedTypeEntity; + } +} diff --git a/src/common/repositories/DeedsHasDocumentTypesRepository.ts b/src/common/repositories/DeedsHasDocumentTypesRepository.ts new file mode 100644 index 00000000..65bac2c0 --- /dev/null +++ b/src/common/repositories/DeedsHasDocumentTypesRepository.ts @@ -0,0 +1,42 @@ +import Database from "@Common/databases/database"; +import { DeedHasDocumentTypes } from "@prisma/client"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; + +@Service() +export default class DeedHasDocumentTypesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().deedHasDocumentTypes; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many deeds + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Find unique relation between deed and a document type + */ + public async findOneByUid(uid: string): Promise { + const deedHasDocumentTypesEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!deedHasDocumentTypesEntity) { + throw new Error("relation between deed and document type not found"); + } + + return deedHasDocumentTypesEntity; + } +} diff --git a/src/common/repositories/DeedsRepository.ts b/src/common/repositories/DeedsRepository.ts new file mode 100644 index 00000000..71c37b47 --- /dev/null +++ b/src/common/repositories/DeedsRepository.ts @@ -0,0 +1,105 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Deeds, Prisma } from "@prisma/client"; +import { Deed } from "le-coffre-resources/dist/Notary"; + +@Service() +export default class DeedsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().deeds; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many users + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a deed based on a deed type + */ + public async create(deed: Deed): Promise { + const createArgs: Prisma.DeedsCreateArgs = { + data: { + deed_type: { + connect: { + uid: deed.deed_type.uid, + }, + }, + }, + }; + const deedTypeWithDocumentTypes = await this.instanceDb.deedTypes.findUniqueOrThrow({ + where: { + uid: deed.deed_type.uid, + }, + include: { deed_type_has_document_types: true }, + }); + + if (deedTypeWithDocumentTypes.archived_at) throw new Error("deed type is archived"); + + if (deedTypeWithDocumentTypes.deed_type_has_document_types) { + createArgs.data.deed_has_document_types = { + createMany: { + data: deedTypeWithDocumentTypes.deed_type_has_document_types.map((relation) => ({ + document_type_uid: relation.document_type_uid, + })), + skipDuplicates: true, + }, + }; + } + return this.model.create(createArgs); + } + + /** + * @description : Update data of a deed type + */ + public async update(uid: string, deed: Deed): Promise { + const updateArgs: Prisma.DeedsUpdateArgs = { + where: { + uid: uid, + }, + data: {}, + include: { + deed_has_document_types: true, + }, + }; + if (deed.deed_has_document_types) { + updateArgs.data.deed_has_document_types = { + deleteMany: { deed_uid: uid }, + createMany: { + data: deed.deed_has_document_types.map((relation) => ({ + document_type_uid: relation.document_type.uid!, + })), + skipDuplicates: true, + }, + }; + } + return this.model.update(updateArgs); + } + + /** + * @description : Find unique deed + */ + public async findOneByUid(uid: string): Promise { + const deedTypeEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!deedTypeEntity) { + throw new Error("deed not found"); + } + + return deedTypeEntity; + } +} diff --git a/src/common/repositories/DocumentTypesRepository.ts b/src/common/repositories/DocumentTypesRepository.ts new file mode 100644 index 00000000..d29259cb --- /dev/null +++ b/src/common/repositories/DocumentTypesRepository.ts @@ -0,0 +1,87 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { DocumentTypes, Prisma } from "prisma/prisma-client"; +import { DocumentType } from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class DocumentTypesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().documentTypes; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many document types + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a document type + */ + public async create(documentType: DocumentType): Promise { + return this.model.create({ + data: { + name: documentType.name, + public_description: documentType.public_description, + private_description: documentType.private_description, + office: { + connect: { + uid: documentType.office.uid, + }, + }, + }, + }); + } + + /** + * @description : update given document type + */ + public async update(uid: string, documentType: DocumentType): Promise { + return this.model.update({ + where: { + uid: uid, + }, + data: { + name: documentType.name, + public_description: documentType.public_description, + private_description: documentType.private_description, + archived_at: documentType.archived_at, + office: { + connect: { + uid: documentType.office.uid, + }, + }, + }, + }); + } + + /** + * @description : find unique document type + */ + public async findOneByUid(uid: string, query?: any): Promise { + const findOneArgs: Prisma.DocumentTypesFindUniqueArgs = { + where: { + uid: uid, + } + }; + if(query) { + findOneArgs.include = query + } + const documentTypeEntity = await this.model.findUnique(findOneArgs); + + if (!documentTypeEntity) { + throw new Error("Document Type not found"); + } + + return documentTypeEntity; + } +} diff --git a/src/common/repositories/DocumentsRepository.ts b/src/common/repositories/DocumentsRepository.ts new file mode 100644 index 00000000..e9c631f2 --- /dev/null +++ b/src/common/repositories/DocumentsRepository.ts @@ -0,0 +1,133 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Documents, EDocumentStatus, Prisma } from "@prisma/client"; +import { Document } from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class DocumentsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().documents; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many documents + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a document + */ + public async create(document: Document): Promise { + const documentCreated = await this.model.create({ + data: { + folder: { + connect: { + uid: document.folder.uid, + }, + }, + depositor: { + connect: { + uid: document.depositor.uid, + }, + }, + document_type: { + connect: { + uid: document.document_type.uid, + }, + }, + }, + }); + + await this.instanceDb.documentHistory.create({ + data: { + document: { + connect: { + uid: documentCreated.uid, + }, + }, + }, + }); + + return documentCreated; + } + + /** + * @description : Create many documents linked to an office folder + */ + public async createMany(documents: Document[]): Promise { + return this.model.createMany({ + data: documents.map((document) => ({ + folder_uid: document.folder.uid!, + depositor_uid: document.depositor.uid!, + document_type_uid: document.document_type.uid!, + })), + skipDuplicates: true, + }); + } + + /** + * @description : Update data of a document + */ + public async update(uid: string, document: Document, refusedReason?: string): Promise { + return this.model.update({ + where: { + uid: uid, + }, + data: { + document_status: EDocumentStatus[document.document_status as keyof typeof EDocumentStatus], + document_history: { + create: { + document_status: EDocumentStatus[document.document_status as keyof typeof EDocumentStatus], + refused_reason: refusedReason, + }, + }, + depositor: { + connect: { + uid: document.depositor.uid, + }, + }, + }, + }); + } + + /** + * @description : Delete a document + */ + public async delete(uid: string): Promise { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } + + /** + * @description : Find unique document + */ + public async findOneByUid(uid: string, query?: any): Promise { + const findOneArgs: Prisma.DocumentsFindUniqueArgs = { + where: { + uid: uid, + } + }; + if(query) { + findOneArgs.include = query + } + const documentEntity = await this.model.findUnique(findOneArgs); + if (!documentEntity) { + throw new Error("Document not found"); + } + + return documentEntity; + } +} diff --git a/src/common/repositories/FilesRepository.ts b/src/common/repositories/FilesRepository.ts new file mode 100644 index 00000000..d91da097 --- /dev/null +++ b/src/common/repositories/FilesRepository.ts @@ -0,0 +1,84 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Files } from "@prisma/client"; +import { File } from "le-coffre-resources/dist/SuperAdmin" + +@Service() +export default class FilesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().files; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many files + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a file linked to a document + */ + public async create(file: File): Promise { + return this.model.create({ + data: { + document: { + connect: { + uid: file.document.uid + } + }, + file_path: file.file_path + }, + }); + } + + /** + * @description : Update data of a file + */ + public async update(uid: string, file: File): Promise { + return this.model.update({ + where: { + uid: uid, + }, + data: { + file_path: file.file_path + }, + }); + } + + /** + * @description : Delete a file + */ + public async delete(uid: string): Promise { + return this.model.delete({ + where: { + uid: uid, + } + }); + } + + /** + * @description : Find unique file + */ + public async findOneByUid(uid: string): Promise { + const fileEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!fileEntity) { + throw new Error("File not found"); + } + + return fileEntity; + } +} diff --git a/src/common/repositories/OfficeFoldersHasCustomerRepository.ts b/src/common/repositories/OfficeFoldersHasCustomerRepository.ts new file mode 100644 index 00000000..aa83e947 --- /dev/null +++ b/src/common/repositories/OfficeFoldersHasCustomerRepository.ts @@ -0,0 +1,43 @@ +import Database from "@Common/databases/database"; +import { OfficeFolderHasCustomers } from "@prisma/client"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; + +@Service() +export default class OfficeFoldersHasCustomerRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().officeFolderHasCustomers; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many relations + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + + /** + * @description : Find a unique relation between an office folder and customers + */ + public async findOneByUid(uid: string): Promise { + const officeFolderHasCustomersEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!officeFolderHasCustomersEntity) { + throw new Error("relation between office folder and customer not found"); + } + + return officeFolderHasCustomersEntity; + } +} diff --git a/src/common/repositories/OfficeFoldersHasStakeholderRepository.ts b/src/common/repositories/OfficeFoldersHasStakeholderRepository.ts new file mode 100644 index 00000000..02b2bb11 --- /dev/null +++ b/src/common/repositories/OfficeFoldersHasStakeholderRepository.ts @@ -0,0 +1,42 @@ +import Database from "@Common/databases/database"; +import { OfficeFolderHasStakeholders } from "@prisma/client"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; + +@Service() +export default class OfficeFoldersHasStakeholderRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().officeFolderHasStakeholders; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many relations + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Find a unique relation between an office folder and stakeholders + */ + public async findOneByUid(uid: string): Promise { + const officeFolderHasStakeholdersEntity = await this.model.findUnique({ + where: { + uid: uid, + }, + }); + + if (!officeFolderHasStakeholdersEntity) { + throw new Error("relation between office folder and stakeholder not found"); + } + + return officeFolderHasStakeholdersEntity; + } +} diff --git a/src/common/repositories/OfficeFoldersRepository.ts b/src/common/repositories/OfficeFoldersRepository.ts new file mode 100644 index 00000000..06e8d87a --- /dev/null +++ b/src/common/repositories/OfficeFoldersRepository.ts @@ -0,0 +1,146 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { EFolderStatus, OfficeFolders, Prisma } from "@prisma/client"; +import { OfficeFolder } from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class OfficeFoldersRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().officeFolders; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many office folders + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create new office folder with stakeholders + */ + public async create(officeFolder: OfficeFolder): Promise { + const createArgs: Prisma.OfficeFoldersCreateArgs = { + data: { + folder_number: officeFolder.folder_number, + name: officeFolder.name, + description: officeFolder.description, + status: EFolderStatus.LIVE, + deed: { + create: { + deed_type: { + connect: { + uid: officeFolder.deed.deed_type.uid, + }, + }, + }, + }, + office: { + connect: { + idNot: officeFolder.office.idNot, + }, + }, + }, + include: { + office_folder_has_stakeholder: true, + } + }; + if (officeFolder.office_folder_has_stakeholder) { + createArgs.data.office_folder_has_stakeholder = { + createMany: { + data: officeFolder.office_folder_has_stakeholder.map((relation) => ({ + user_stakeholder_uid: relation.user_stakeholder.uid!, + })), + skipDuplicates: true + }, + }; + } + return this.model.create(createArgs); + } + + /** + * @description : Update data of an office folder + */ + public async update(officeFolderuid: string, officeFolder: OfficeFolder): Promise { + const updateArgs: Prisma.OfficeFoldersUpdateArgs = { + where: { + uid: officeFolderuid, + }, + data: { + folder_number: officeFolder.folder_number, + name: officeFolder.name, + description: officeFolder.description, + status: EFolderStatus[officeFolder.status as keyof typeof EFolderStatus], + archived_description: officeFolder.archived_description, + }, + include: { + office_folder_has_stakeholder: true, + office_folder_has_customers: true, + documents: true, + } + }; + if (officeFolder.office_folder_has_stakeholder) { + updateArgs.data.office_folder_has_stakeholder = { + deleteMany: { office_folder_uid: officeFolderuid }, + createMany: { + data: officeFolder.office_folder_has_stakeholder.map((relation) => ({ + user_stakeholder_uid: relation.user_stakeholder.uid!, + })), + skipDuplicates: true + }, + } + } + if (officeFolder.office_folder_has_customers) { + updateArgs.data.office_folder_has_customers = { + deleteMany: { office_folder_uid: officeFolderuid }, + createMany: { + data: officeFolder.office_folder_has_customers.map((relation) => ({ + customer_uid: relation.customer.uid!, + })), + skipDuplicates: true + }, + } + } + if (officeFolder.documents) { + updateArgs.data.documents = { + createMany: { + data: officeFolder.documents.map((relation) => ({ + document_type_uid: relation.document_type.uid!, + depositor_uid: relation.depositor.uid! + })), + skipDuplicates: true + }, + } + } + return this.model.update(updateArgs); + } + + /** + * @description : Find one office folder + */ + public async findOneByUid(uid: string, query?: any): Promise { + const findOneArgs: Prisma.OfficeFoldersFindUniqueArgs = { + where: { + uid: uid, + } + }; + if(query) { + findOneArgs.include = query + } + const officeFolderEntity = await this.model.findUnique(findOneArgs); + + if (!officeFolderEntity) { + throw new Error("office folder not found"); + } + + return officeFolderEntity; + } +} diff --git a/src/common/repositories/OfficesRepository.ts b/src/common/repositories/OfficesRepository.ts new file mode 100644 index 00000000..1bd11cf0 --- /dev/null +++ b/src/common/repositories/OfficesRepository.ts @@ -0,0 +1,92 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { EOfficeStatus, Offices, Prisma } from "@prisma/client"; +import { Office as OfficeRessource } from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class OfficesRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().offices; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many users + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + + return this.model.findMany(query); + } + + /** + * @description : Create an office + */ + public async create(office: OfficeRessource): Promise { + return this.model.create({ + data: { + idNot: office.idNot, + name: office.name, + crpcen: office.crpcen, + address: { + create: { + address: office.address.address, + zip_code: office.address.zip_code, + city: office.address.city, + }, + }, + office_status: EOfficeStatus.DESACTIVATED, + }, + }); + } + + /** + * @description : Update data from an office + */ + public async update(uid: string, office: OfficeRessource): Promise { + return this.model.update({ + where: { + uid: uid, + }, + data: { + name: office.name, + address: { + create: { + address: office.address.address, + zip_code: office.address.zip_code, + city: office.address.city, + }, + }, + office_status: EOfficeStatus[office.office_status as keyof typeof EOfficeStatus], + }, + }); + } + + /** + * @description : Find one office + */ + public async findOneByUid(uid: string, query?: any): Promise { + const findOneArgs: Prisma.OfficesFindUniqueArgs = { + where: { + uid: uid, + } + }; + if(query) { + findOneArgs.include = query + } + const officeEntity = await this.model.findUnique(findOneArgs); + + + if (!officeEntity) { + throw new Error("office not found"); + } + + return officeEntity; + } +} diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts new file mode 100644 index 00000000..72a521c0 --- /dev/null +++ b/src/common/repositories/UsersRepository.ts @@ -0,0 +1,148 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { ECivility, Prisma, Users } from "@prisma/client"; +import User from "le-coffre-resources/dist/SuperAdmin"; + +@Service() +export default class UsersRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().users; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many users + */ + public async findMany(query: any): Promise { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : Create a user + */ + public async create(user: User): Promise { + const createArgs: Prisma.UsersCreateArgs = { + data: { + idNot: user.idNot, + office_membership: { + connectOrCreate: { + where: { + idNot: user.office_membership.idNot, + }, + create: { + idNot: user.office_membership.idNot, + name: user.office_membership.name, + crpcen: user.office_membership.crpcen, + address: { + create: { + address: user.office_membership.address.address, + zip_code: user.office_membership.address.zip_code, + city: user.office_membership.address.city, + }, + }, + }, + }, + }, + contact: { + create: { + first_name: user.contact.first_name, + last_name: user.contact.last_name, + email: user.contact.email, + phone_number: user.contact.phone_number, + cell_phone_number: user.contact.cell_phone_number, + civility: ECivility[user.contact.civility as keyof typeof ECivility], + address: {}, + }, + }, + }, + } + if (user.contact.address) { + createArgs.data.contact!.create!.address!.create = { + address: user.contact.address!.address, + zip_code: user.contact.address!.zip_code, + city: user.contact.address!.city, + }; + } + return this.model.create(createArgs); + } + + /** + * @description : Update data from a user + */ + public async update(uid: string, user: User): Promise { + const updateArgs: Prisma.UsersUpdateArgs = { + where: { + uid: uid, + }, + data: { + idNot: user.idNot, + office_membership: { + connectOrCreate: { + where: { + idNot: user.office_membership.idNot, + }, + create: { + idNot: user.office_membership.idNot, + name: user.office_membership.name, + crpcen: user.office_membership.crpcen, + address: { + create: { + address: user.office_membership.address.address, + zip_code: user.office_membership.address.zip_code, + city: user.office_membership.address.city, + }, + }, + }, + }, + }, + contact: { + update: { + first_name: user.contact.first_name, + last_name: user.contact.last_name, + email: user.contact.email, + phone_number: user.contact.phone_number, + cell_phone_number: user.contact.cell_phone_number, + civility: ECivility[user.contact.civility as keyof typeof ECivility], + address: {} + }, + }, + }, + }; + if (user.contact.address) { + updateArgs.data.contact!.update!.address!.update = { + address: user.contact.address!.address, + zip_code: user.contact.address!.zip_code, + city: user.contact.address!.city, + }; + } + return this.model.update(updateArgs); + } + + /** + * @description : Find one user + */ + public async findOneByUid(uid: string, query?: any): Promise { + const findOneArgs: Prisma.UsersFindUniqueArgs = { + where: { + uid: uid, + } + }; + if(query) { + findOneArgs.include = query + } + const userEntity = await this.model.findUnique(findOneArgs); + + if (!userEntity) { + throw new Error("User not found"); + } + + return userEntity; + } +} diff --git a/src/common/system/ExpressServer.ts b/src/common/system/ExpressServer.ts new file mode 100644 index 00000000..ec8e217b --- /dev/null +++ b/src/common/system/ExpressServer.ts @@ -0,0 +1,33 @@ +import express, { Express, Router } from "express"; +import { Service } from "typedi"; +import ServerInterface, { IConfig } from "./ServerInterface"; + +@Service() +export default class ExpressServer implements ServerInterface { + private router: Express = express(); + private subRouter: Router = express.Router(); + + public getRouter(): Router { + return this.subRouter; + } + + public init(config: IConfig) { + this.router.use(...config.middlwares); + this.router.use(config.rootUrl, this.subRouter); + if (config.errorHandler) this.router.use(config.errorHandler); + + this.router.listen(config.port, () => { + console.table( + [ + { + "Entry label": config.label, + Port: config.port, + "Root url": config.rootUrl, + }, + ], + ["Entry label", "Port", "Root url"], + ); + }); + return this; + } +} diff --git a/src/common/system/ExpressServerTest.ts b/src/common/system/ExpressServerTest.ts new file mode 100644 index 00000000..abf20b60 --- /dev/null +++ b/src/common/system/ExpressServerTest.ts @@ -0,0 +1,39 @@ +import express, { Express, Router } from "express"; +import { Service } from "typedi"; +import ServerInterface, { IConfig } from "./ServerInterface"; + +@Service() +export default class ExpressServer implements ServerInterface { + public router: Express = express(); + private subRouter: Router = express.Router(); + + public getRouter(): Router { + return this.subRouter; + } + + protected getMainRouter(): Express { + return this.router; + } + + public init(config: IConfig) { + this.router.use(...config.middlwares); + this.router.use(config.rootUrl, this.subRouter); + if (config.errorHandler) this.router.use(config.errorHandler); + return this; + } + + public listen() { + return this.router.listen(3001, () => { + console.table( + [ + { + "Entry label": "le coffre API", + Port: 3001, + "Root url": "/api", + }, + ], + ["Entry label", "Port", "Root url"], + ); + }); + } +} diff --git a/src/common/system/NextJs.ts b/src/common/system/NextJs.ts new file mode 100644 index 00000000..8aab43bc --- /dev/null +++ b/src/common/system/NextJs.ts @@ -0,0 +1,50 @@ +import express, { Express } from "express"; +import { Service } from "typedi"; +import next from "next"; +import url from "url"; + +interface IConfig { + label: string; + isDev: boolean; + port: number; + rootUrl: string; +} + +@Service() +export default class Server { + private router: Express = express(); + constructor() {} + + public getRouter(): Express { + return this.router; + } + + public init(config: IConfig) { + const app = next({ dev: config.isDev }); + const handler = app.getRequestHandler(); + app.prepare().then(() => { + const subRouter = express.Router(); + subRouter.get("/*", async (req, res) => { + const parsedUrl = url.parse(req.url, true); + await handler(req, res, parsedUrl); + }); + + this.router.use(config.rootUrl, subRouter); + + this.router.listen(config.port, () => { + console.table( + [ + { + "Entry label": config.label, + Port: config.port, + "Root url": config.rootUrl, + }, + ], + ["Entry label", "Port", "Root url"], + ); + }); + }); + + return this; + } +} diff --git a/src/common/system/ServerInterface.ts b/src/common/system/ServerInterface.ts new file mode 100644 index 00000000..e3962a03 --- /dev/null +++ b/src/common/system/ServerInterface.ts @@ -0,0 +1,16 @@ +import { NextFunction, Request, Response, Router } from "express"; +import { RequestHandlerParams } from "express-serve-static-core"; + +export interface IConfig { + label: string; + port: number; + rootUrl: string; + middlwares: RequestHandlerParams[]; + errorHandler?: (error: any, req: Request, res: Response, next: NextFunction) => void; +} + +export default interface ServerInterface { + getRouter(): Router; + + init(config: IConfig): this; +} diff --git a/src/common/system/controller-pattern/ApiController.ts b/src/common/system/controller-pattern/ApiController.ts new file mode 100644 index 00000000..3f8458c7 --- /dev/null +++ b/src/common/system/controller-pattern/ApiController.ts @@ -0,0 +1,8 @@ +import { Service } from "typedi"; +import BaseController from "@Common/system/controller-pattern/BaseController"; +import HttpCodes from "@Common/system/controller-pattern/HttpCodes"; + +@Service() +export default abstract class ApiController extends BaseController {} + +export { HttpCodes as ResponseStatusCodes }; diff --git a/src/common/system/controller-pattern/BaseController.ts b/src/common/system/controller-pattern/BaseController.ts new file mode 100644 index 00000000..3b128b1f --- /dev/null +++ b/src/common/system/controller-pattern/BaseController.ts @@ -0,0 +1,41 @@ +import { StRoute } from "./StRoute"; +import { Response } from "express"; +import HttpCodes from "@Common/system/controller-pattern/HttpCodes"; + +type IResponseData = {} | string | number | boolean | null | unknown; + +export default abstract class BaseController { + public expressRoutes!: StRoute[]; + public httpCode: typeof HttpCodes = HttpCodes; + + protected httpSuccess(response: Response, responseData: IResponseData = null) { + return this.httpResponse(response, HttpCodes.SUCCESS, responseData); + } + + protected httpCreated(response: Response, responseData: IResponseData = null) { + return this.httpResponse(response, HttpCodes.CREATED, responseData); + } + + protected httpBadRequest(response: Response, responseData: IResponseData = "Http Bad Request") { + return this.httpResponse(response, HttpCodes.BAD_REQUEST, responseData); + } + + protected httpNotFoundRequest(response: Response, responseData: IResponseData = "Not Found") { + return this.httpResponse(response, HttpCodes.NOT_FOUND, responseData); + } + + protected httpInternaleError(response: Response, responseData: IResponseData = "http Internal Server Error") { + return this.httpResponse(response, HttpCodes.INTERNAL_ERROR, responseData); + } + + protected httpNotImplemented(response: Response, responseData: IResponseData = "http Internal Server Error") { + return this.httpResponse(response, HttpCodes.NOT_IMPLEMENTED, responseData); + } + + protected httpResponse(response: Response, httpCode: HttpCodes, responseData: IResponseData = {}) { + if (responseData instanceof Error) { + throw responseData; + } + return response.status(httpCode).send(responseData); + } +} diff --git a/src/common/system/controller-pattern/Controller.ts b/src/common/system/controller-pattern/Controller.ts new file mode 100644 index 00000000..91d5789f --- /dev/null +++ b/src/common/system/controller-pattern/Controller.ts @@ -0,0 +1,36 @@ +import { type Response, type Request, type NextFunction } from "express"; +import Container from "typedi"; +import ExpressServer from "../ExpressServer"; +import type BaseController from "./BaseController"; +import ErrorCatch from "./ErrorCatch"; +import { StRoute } from "./StRoute"; + +/** + * @description Decorator to supports defining requests handler for Express on the class. Its fully support TypeScript and dependency injection. + */ +function Controller() { + return (constructor: T) => { + const controller = Container.get(constructor); + if (!controller.expressRoutes || !Array.isArray(controller.expressRoutes)) return; + controller.expressRoutes.forEach((route) => createRoute(controller, route)); + }; +} + +function createRoute(controller: any, route: StRoute) { + const server: ExpressServer = Container.get(ExpressServer); + const errorCatch = Container.get(ErrorCatch); + const args = [ + ...route.frontMiddlewares, + async (req: Request, res: Response, next: NextFunction) => { + try { + await route.func.call(controller, req, res); + } catch (error) { + errorCatch.handle(req, res, next, error); + } + }, + ...route.backMiddlewares, + ]; + server.getRouter()[route.type](route.path, ...args); +} + +export default Controller; diff --git a/src/common/system/controller-pattern/ErrorCatch.ts b/src/common/system/controller-pattern/ErrorCatch.ts new file mode 100644 index 00000000..8dc0aea4 --- /dev/null +++ b/src/common/system/controller-pattern/ErrorCatch.ts @@ -0,0 +1,14 @@ +import { Request, Response, NextFunction } from "express"; +import { Service } from "typedi"; + +/** + * Capture primitive errors to preserve application crash + */ +@Service() +export default class ErrorCatch { + constructor() {} + + public handle(request: Request, response: Response, next: NextFunction, ...args: any[]): void { + next(args[args.length - 1] ?? "Unknown Error"); + } +} diff --git a/src/common/system/controller-pattern/HttpCodes.ts b/src/common/system/controller-pattern/HttpCodes.ts new file mode 100644 index 00000000..8ca2500b --- /dev/null +++ b/src/common/system/controller-pattern/HttpCodes.ts @@ -0,0 +1,10 @@ +enum HttpCodes { + SUCCESS = 200, + CREATED = 201, + BAD_REQUEST = 400, + INTERNAL_ERROR = 500, + UNKNOWN_ERROR = 520, + NOT_IMPLEMENTED = 501, + NOT_FOUND = 404, +} +export default HttpCodes; diff --git a/src/common/system/controller-pattern/Methods.ts b/src/common/system/controller-pattern/Methods.ts new file mode 100644 index 00000000..43c0f6f0 --- /dev/null +++ b/src/common/system/controller-pattern/Methods.ts @@ -0,0 +1,36 @@ +import BaseController from "./BaseController"; +import { StRoute } from "./StRoute"; + +function MethodsAny( + type: StRoute["type"], + path: string, + frontMiddlewares: StRoute["frontMiddlewares"] = [], + backMiddlewares: StRoute["backMiddlewares"] = [], +) { + return (target: any, memberName: string, propertyDescriptor: PropertyDescriptor) => { + const func = propertyDescriptor.value; + const constructor: typeof BaseController = target.constructor; + constructor.prototype.expressRoutes ??= []; + constructor.prototype.expressRoutes.push({ type, path, func, frontMiddlewares, backMiddlewares }); + }; +} + +/** + * @description Decorator Method GET + */ +export const Get = MethodsAny.bind(null, "get"); + +/** + * @description Decorator Method POST + */ +export const Post = MethodsAny.bind(null, "post"); + +/** + * @description Decorator Method DELETE + */ +export const Delete = MethodsAny.bind(null, "delete"); + +/** + * @description Decorator Method PUT + */ +export const Put = MethodsAny.bind(null, "put"); diff --git a/src/common/system/controller-pattern/StRoute.ts b/src/common/system/controller-pattern/StRoute.ts new file mode 100644 index 00000000..fe6ae78d --- /dev/null +++ b/src/common/system/controller-pattern/StRoute.ts @@ -0,0 +1,9 @@ +import { type Request, type Response, type NextFunction } from "express"; + +export interface StRoute { + type: "get" | "post" | "delete" | "put"; + path: string; + func: (requests: Request, response: Response) => Promise; + frontMiddlewares: ((requests: Request, response: Response, next: NextFunction) => void)[]; + backMiddlewares: ((requests: Request, response: Response, next: NextFunction) => void)[]; +} diff --git a/src/common/system/controller-pattern/exceptions/HttpException.ts b/src/common/system/controller-pattern/exceptions/HttpException.ts new file mode 100644 index 00000000..62a485b7 --- /dev/null +++ b/src/common/system/controller-pattern/exceptions/HttpException.ts @@ -0,0 +1,7 @@ +import HttpCodes from "../HttpCodes"; + +export default class HttpException extends Error { + constructor(message: string, public httpCode: HttpCodes = HttpCodes.UNKNOWN_ERROR) { + super(message); + } +} diff --git a/src/common/system/controller-pattern/index.ts b/src/common/system/controller-pattern/index.ts new file mode 100644 index 00000000..9e70b158 --- /dev/null +++ b/src/common/system/controller-pattern/index.ts @@ -0,0 +1,2 @@ +export { default as Controller } from "./Controller"; +export * from "./Methods"; diff --git a/src/common/system/database/DbProvider.ts b/src/common/system/database/DbProvider.ts new file mode 100644 index 00000000..fa640f45 --- /dev/null +++ b/src/common/system/database/DbProvider.ts @@ -0,0 +1,36 @@ +import { BackendVariables } from "@Common/config/variables/Variables"; +import { PrismaClient } from "@prisma/client"; +import dotenv from "dotenv"; +import Container from "typedi"; + +import IDatabaseConfig from "../../config/database/IDatabaseConfig"; + +dotenv.config(); + +export default class DbProvider { + protected readonly variables = Container.get(BackendVariables); + protected url = `postgres://${this.variables.DATABASE_USERNAME}:${this.variables.DATABASE_PASSWORD}@${this.variables.DATABASE_HOST}:${this.variables.DATABASE_PORT}/${this.variables.DATABASE_NAME}`; + protected client = new PrismaClient({ + datasources: { + db: { + url: this.url, + }, + }, + }); + + constructor(protected config: IDatabaseConfig) {} + + public async connect(): Promise { + await this.client.$connect(); + console.info(`⚡️[Prisma]: Connected to ${this.config.name}`); // A Logger middleware is to be added here + } + + public getClient() { + return this.client; + } + + public async disconnect(): Promise { + await this.client.$disconnect(); + console.info(`⚡️[Prisma]: Disconnected from ${this.config.name}`); // A Logger middleware is to be added here + } +} diff --git a/src/common/system/database/exceptions/ORMBadQueryError.ts b/src/common/system/database/exceptions/ORMBadQueryError.ts new file mode 100644 index 00000000..89255e6a --- /dev/null +++ b/src/common/system/database/exceptions/ORMBadQueryError.ts @@ -0,0 +1,5 @@ +export class ORMBadQueryError extends Error { + constructor(message: string, public error: Error) { + super(message); + } +} diff --git a/src/common/system/database/index.ts b/src/common/system/database/index.ts new file mode 100644 index 00000000..943586b8 --- /dev/null +++ b/src/common/system/database/index.ts @@ -0,0 +1,5 @@ +import IDatabaseConfig from "@Common/config/database/IDatabaseConfig"; +import DbProvider from "./DbProvider"; + +export type { IDatabaseConfig }; +export default DbProvider; diff --git a/src/entries/App.ts b/src/entries/App.ts new file mode 100644 index 00000000..b520f7a0 --- /dev/null +++ b/src/entries/App.ts @@ -0,0 +1,33 @@ +import "module-alias/register"; +import "reflect-metadata"; +import { Container } from "typedi"; +import ExpressServer from "@Common/system/ExpressServer"; +import routes from "@App/index"; +import cors from "cors"; +import bodyParser from "body-parser"; +// import TezosLink from "@Common/databases/TezosLink"; +import errorHandler from "@App/middlewares/ErrorHandler"; +import { BackendVariables } from "@Common/config/variables/Variables"; + +(async () => { + try { + const variables = await Container.get(BackendVariables).validate(); + + const port = variables.APP_PORT; + const rootUrl = variables.APP_ROOT_URL; + const label = variables.APP_LABEL ?? "Unknown Service"; + + // Container.get(TezosLink).connect(); + Container.get(ExpressServer).init({ + label, + port: parseInt(port), + rootUrl, + middlwares: [cors({ origin: "*" }), bodyParser.urlencoded({ extended: true }), bodyParser.json()], + errorHandler, + }); + + routes.start(); + } catch (e) { + console.error(e); + } +})(); diff --git a/src/services/BaseService.ts b/src/services/BaseService.ts new file mode 100644 index 00000000..0ff263c1 --- /dev/null +++ b/src/services/BaseService.ts @@ -0,0 +1,6 @@ +export default abstract class BaseService { + /** @TODO place methods in a config file */ + public static readonly whitelisted: string[] = ["/chains/main/blocks"]; + public static readonly blacklisted: string[] = ["/context/contracts", "/monitor", "/network"]; + public static readonly rollingPatterns: string[] = ["/head", "/injection/operation"]; +} diff --git a/src/services/private-services/AddressesService/AddressesService.ts b/src/services/private-services/AddressesService/AddressesService.ts new file mode 100644 index 00000000..41e53765 --- /dev/null +++ b/src/services/private-services/AddressesService/AddressesService.ts @@ -0,0 +1,26 @@ +import AddressesRepository from "@Repositories/AddressesRepository"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; + +@Service() +export default class AddressesService extends BaseService { + constructor(private addressRepository: AddressesRepository) { + super(); + } + + /** + * @description : Get all addresses + * @throws {Error} If addresses cannot be get + */ + public async get(query: any) { + return this.addressRepository.findMany(query); + } + + /** + * @description : Get a address by uid + * @throws {Error} If address cannot be get + */ + public async getByUid(uid: string) { + return this.addressRepository.findOneByUid(uid); + } +} diff --git a/src/services/private-services/AuthService/AuthService.ts b/src/services/private-services/AuthService/AuthService.ts new file mode 100644 index 00000000..5470dba2 --- /dev/null +++ b/src/services/private-services/AuthService/AuthService.ts @@ -0,0 +1,54 @@ +import jwt from "jsonwebtoken"; +import BaseService from "@Services/BaseService"; +import "reflect-metadata"; +import { BackendVariables } from "@Common/config/variables/Variables"; +import Container, { Service } from "typedi"; + +type IdNotTokens = { + access_token: string; + id_token: string; +}; + +@Service() +export default class AuthService extends BaseService { + protected readonly variables = Container.get(BackendVariables); + private constructor() { + super(); + } + + /** + * @description : Get IdNot id_token and access_token + * @throws {Error} If jwt pair cannot be get + */ + public async getUserFromIdNotTokens(code: string) { + const tokens = await this.getIdNotTokens(code); + return jwt.decode(tokens.id_token); + } + + private async getIdNotTokens(code: string): Promise { + const url = new URL( + this.variables.IDNOT_CONNEXION_URL.concat("?") + + new URLSearchParams({ + client_id: this.variables.IDNOT_CLIENT_ID, + client_secret: this.variables.IDNOT_CLIENT_SECRET, + redirect_uri: this.variables.IDNOT_REDIRECT_URL, + code: code, + grant_type: "authorization_code", + }), + ); + try { + const headers = new Headers({ + "Content-Type": "application/x-www-form-urlencoded", + }); + const res = await fetch(url, { + method: "POST", + headers: headers, + }); + const data = await res.json(); + return data as IdNotTokens; + } catch (error) { + console.log(error); + throw new Error(); + } + } +} diff --git a/src/services/private-services/ContactsService/ContactsService.ts b/src/services/private-services/ContactsService/ContactsService.ts new file mode 100644 index 00000000..4d381a84 --- /dev/null +++ b/src/services/private-services/ContactsService/ContactsService.ts @@ -0,0 +1,26 @@ +import ContactsRepository from "@Repositories/ContactsRepository"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; + +@Service() +export default class ContactsService extends BaseService { + constructor(private contactRepository: ContactsRepository) { + super(); + } + + /** + * @description : Get all contacts + * @throws {Error} If contacts cannot be get + */ + public async get(query: any) { + return this.contactRepository.findMany(query); + } + + /** + * @description : Get a contact by uid + * @throws {Error} If contact cannot be get + */ + public async getByUid(uid: string) { + return this.contactRepository.findOneByUid(uid); + } +} diff --git a/src/services/private-services/FilesService/FilesService.ts b/src/services/private-services/FilesService/FilesService.ts new file mode 100644 index 00000000..67656f16 --- /dev/null +++ b/src/services/private-services/FilesService/FilesService.ts @@ -0,0 +1,43 @@ +import FilesRepository from "@Repositories/FilesRepository"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; +import { File } from "le-coffre-resources/dist/SuperAdmin" + +@Service() +export default class FilesService extends BaseService { + constructor(private filesRepository: FilesRepository) { + super(); + } + + /** + * @description : Get all files + * @throws {Error} If files cannot be ge + */ + public async get(query: any) { + return this.filesRepository.findMany(query); + } + + /** + * @description : Create a new file + * @throws {Error} If file cannot be created + */ + public async create(file: File) { + return this.filesRepository.create(file); + } + + /** + * @description : Modify a new file + * @throws {Error} If file cannot be modified + */ + public async put(uid: string, file: File) { + return this.filesRepository.update(uid, file); + } + + /** + * @description : Get a file by uid + * @throws {Error} If project cannot be created + */ + public async getByUid(uid: string) { + return this.filesRepository.findOneByUid(uid); + } +} diff --git a/src/services/private-services/NotificationsService/NotificationsService.ts b/src/services/private-services/NotificationsService/NotificationsService.ts new file mode 100644 index 00000000..4187e937 --- /dev/null +++ b/src/services/private-services/NotificationsService/NotificationsService.ts @@ -0,0 +1,57 @@ +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; + +@Service() +export default class NotificationsService extends BaseService { + constructor() { + super(); + } + + /** + * @description : Get all notifications + * @returns : T + * @throws {Error} If notifications cannot be get + * @param : projectEntity: Partial + */ + public async get() { + // const notifications = await this.usersRepository.findOne(uuid); + // if (!notifications) Promise.reject(new Error("Cannot get notifications")); + return { response: "/api/notifications > GET : All notifications > Not implemented yet" }; + } + + /** + * @description : Create a new notification + * @returns : T + * @throws {Error} If notification cannot be created + * @param : projectEntity: Partial + */ + public async create() { + // const notification = await this.projectRepository.create(projectEntity); + // if (!notification) Promise.reject(new Error("Cannot create project")); + return { response: "/api/notifications > POST : Create notification > Not implemented yet" }; + } + + /** + * @description : Modify a new notification + * @returns : T + * @throws {Error} If notification cannot be modified + * @param : projectEntity: Partial + */ + public async put() { + // const notification = await this.projectRepository.create(projectEntity); + // if (!notification) Promise.reject(new Error("Cannot create project")); + return { response: "/api/notifications > PUT : Modified notification > Not implemented yet" }; + } + + /** + * @description : Get a notification by uid + * @returns : T + * @throws {Error} If project cannot be created + * @param : projectEntity: Partial + */ + public async getByUid(uid: string) { + // const notification = await this.usersRepository.findOne(uid); + // if (!notification) Promise.reject(new Error("Cannot get notification by uid")); + return { response: "/api/notifications/:uid > GET : notification by uid > Not implemented yet" }; + } +} diff --git a/src/services/super-admin/CustomersService/CustomersService.ts b/src/services/super-admin/CustomersService/CustomersService.ts new file mode 100644 index 00000000..20470182 --- /dev/null +++ b/src/services/super-admin/CustomersService/CustomersService.ts @@ -0,0 +1,44 @@ +import { Customers } from "@prisma/client"; +import CustomersRepository from "@Repositories/CustomersRepository"; +import BaseService from "@Services/BaseService"; +import { Customer } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; + +@Service() +export default class CustomersService extends BaseService { + constructor(private customerRepository: CustomersRepository) { + super(); + } + + /** + * @description : Get all Customers + * @throws {Error} If Customers cannot be get + */ + public async get(query: any) { + return this.customerRepository.findMany(query); + } + + /** + * @description : Create a new customer + * @throws {Error} If customer cannot be created + */ + public async create(customerEntity: Customer): Promise { + return this.customerRepository.create(customerEntity); + } + + /** + * @description : Modify a customer + * @throws {Error} If customer cannot be modified + */ + public async update(uid: string, customerEntity: Customer): Promise { + return this.customerRepository.update(uid, customerEntity); + } + + /** + * @description : Get a customer by uid + * @throws {Error} If customer cannot be get by uid + */ + public async getByUid(uid: string, query?: any): Promise { + return this.customerRepository.findOneByUid(uid, query); + } +} diff --git a/src/services/super-admin/DeedTypesService/DeedTypesService.ts b/src/services/super-admin/DeedTypesService/DeedTypesService.ts new file mode 100644 index 00000000..da71909e --- /dev/null +++ b/src/services/super-admin/DeedTypesService/DeedTypesService.ts @@ -0,0 +1,46 @@ +import { DeedTypes } from "@prisma/client"; +import DeedTypesRepository from "@Repositories/DeedTypesRepository"; +import BaseService from "@Services/BaseService"; +import { DeedType } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; + +@Service() +export default class DeedTypesService extends BaseService { + constructor( + private deedTypeRepository: DeedTypesRepository, + ) { + super(); + } + + /** + * @description : Get all deed-types + * @throws {Error} If deed-types cannot be get + */ + public async get(query: any): Promise { + return this.deedTypeRepository.findMany(query); + } + + /** + * @description : Create a new deed-type + * @throws {Error} If deed-type cannot be created + */ + public async create(deedTypeEntity: DeedType): Promise { + return this.deedTypeRepository.create(deedTypeEntity); + } + + /** + * @description : Modify a deed-type + * @throws {Error} If deed-type cannot be modifified + */ + public async update(uid: string, deedTypeEntity: DeedType): Promise { + return this.deedTypeRepository.update(uid, deedTypeEntity); + } + + /** + * @description : Get a deedtype by uid + * @throws {Error} If deed-type cannot be get by uid + */ + public async getByUid(uid: string, query?: any) { + return this.deedTypeRepository.findOneByUid(uid, query); + } +} diff --git a/src/services/super-admin/DeedsService/DeedsService.ts b/src/services/super-admin/DeedsService/DeedsService.ts new file mode 100644 index 00000000..bb133369 --- /dev/null +++ b/src/services/super-admin/DeedsService/DeedsService.ts @@ -0,0 +1,44 @@ +import { Deeds } from "@prisma/client"; +import DeedsRepository from "@Repositories/DeedsRepository"; +import BaseService from "@Services/BaseService"; +import { Deed } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; + +@Service() +export default class DeedsService extends BaseService { + constructor(private deedRepository: DeedsRepository) { + super(); + } + + /** + * @description : Get all deeds + * @throws {Error} If deeds cannot be get + */ + public async get(query: any) { + return this.deedRepository.findMany(query); + } + + /** + * @description : Create a new deed with document types + * @throws {Error} If deeds cannot be created + */ + public async create(deed: Deed): Promise { + return this.deedRepository.create(deed); + } + + /** + * @description : Update data of a deed with document types + * @throws {Error} If deeds cannot be updated with document types or one of them + */ + public async update(deeduid: string, deed: Deed): Promise { + return this.deedRepository.update(deeduid, deed); + } + + /** + * @description : Get a deed by uid + * @throws {Error} If deed-type cannot be get by uid + */ + public async getByUid(uid: string) { + return this.deedRepository.findOneByUid(uid); + } +} diff --git a/src/services/super-admin/DocumentTypesService/DocumentTypesService.ts b/src/services/super-admin/DocumentTypesService/DocumentTypesService.ts new file mode 100644 index 00000000..dacfe17f --- /dev/null +++ b/src/services/super-admin/DocumentTypesService/DocumentTypesService.ts @@ -0,0 +1,44 @@ +import { DocumentTypes } from "@prisma/client"; +import DocumentTypesRepository from "@Repositories/DocumentTypesRepository"; +import BaseService from "@Services/BaseService"; +import { DocumentType } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; + +@Service() +export default class DocumentTypesService extends BaseService { + constructor(private documentTypeRepository: DocumentTypesRepository) { + super(); + } + + /** + * @description : Get all document-types + * @throws {Error} If document-types cannot be get + */ + public async get(query: any) { + return this.documentTypeRepository.findMany(query); + } + + /** + * @description : Create a new document-type + * @throws {Error} If document-types cannot be created + */ + public async create(documentTypeEntity: DocumentType): Promise { + return this.documentTypeRepository.create(documentTypeEntity); + } + + /** + * @description : Modify a document-type + * @throws {Error} If document-type cannot be modified + */ + public async update(uid: string, documentTypeEntity: DocumentType): Promise { + return this.documentTypeRepository.update(uid, documentTypeEntity); + } + + /** + * @description : Get a document-type by uid + * @throws {Error} If document-type is not found + */ + public async getByUid(uid: string, query?: any) { + return this.documentTypeRepository.findOneByUid(uid, query); + } +} diff --git a/src/services/super-admin/DocumentsService/DocumentsService.ts b/src/services/super-admin/DocumentsService/DocumentsService.ts new file mode 100644 index 00000000..4e466535 --- /dev/null +++ b/src/services/super-admin/DocumentsService/DocumentsService.ts @@ -0,0 +1,60 @@ +import { Documents, Prisma } from "@prisma/client"; +import { Document } from "le-coffre-resources/dist/SuperAdmin"; +import DocumentsRepository from "@Repositories/DocumentsRepository"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; + +@Service() +export default class DocumentsService extends BaseService { + constructor(private documentsRepository: DocumentsRepository) { + super(); + } + + /** + * @description : Get all documents + * @throws {Error} If documents cannot be get + */ + public async get(query: any) { + return this.documentsRepository.findMany(query); + } + + /** + * @description : Create a new document + * @throws {Error} If document cannot be created + */ + public async create(document: Document): Promise { + return this.documentsRepository.create(document); + } + + /** + * @description : Create new documents + * @throws {Error} If documents or one of them cannot be created + */ + public async createMany(documents: Document[]): Promise { + return this.documentsRepository.createMany(documents); + } + + /** + * @description : Modify a document + * @throws {Error} If document cannot be modified + */ + public async update(uid: string, document: Document): Promise { + return this.documentsRepository.update(uid, document); + } + + /** + * @description : Delete a document + * @throws {Error} If document cannot be deleted + */ + public async delete(uid: string): Promise { + return this.documentsRepository.delete(uid); + } + + /** + * @description : Get a document by uid + * @throws {Error} If document cannot be get by uid + */ + public async getByUid(uid: string, query?: any) { + return this.documentsRepository.findOneByUid(uid, query); + } +} diff --git a/src/services/super-admin/OfficeFoldersService/OfficeFoldersService.ts b/src/services/super-admin/OfficeFoldersService/OfficeFoldersService.ts new file mode 100644 index 00000000..06388662 --- /dev/null +++ b/src/services/super-admin/OfficeFoldersService/OfficeFoldersService.ts @@ -0,0 +1,51 @@ +import { OfficeFolders } from ".prisma/client"; +import OfficeFoldersRepository from "@Repositories/OfficeFoldersRepository"; +import BaseService from "@Services/BaseService"; +import { OfficeFolder } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; +import DeedTypesService from "../DeedTypesService/DeedTypesService"; + + +@Service() +export default class OfficeFoldersService extends BaseService { + constructor( + private officeFoldersRepository: OfficeFoldersRepository, + private deedTypeService: DeedTypesService + ) { + super(); + } + + /** + * @description : Get all folders + * @throws {Error} If folders cannot be get + */ + public async get(query: any) { + return this.officeFoldersRepository.findMany(query); + } + + /** + * @description : Create a new folder + * @throws {Error} If folder cannot be created + */ + public async create(officeFolderEntity: OfficeFolder): Promise { + const deedType = await this.deedTypeService.getByUid(officeFolderEntity.deed.deed_type.uid!); + if(deedType.archived_at) throw new Error('deed type is archived'); + return this.officeFoldersRepository.create(officeFolderEntity); + } + + /** + * @description : Modify a folder + * @throws {Error} If folder cannot be modified + */ + public async update(officeFolderuid: string, officeFolderEntity: OfficeFolder): Promise { + return this.officeFoldersRepository.update(officeFolderuid, officeFolderEntity); + } + + /** + * @description : Get a folder by uid + * @throws {Error} If folder cannot be get by uid + */ + public async getByUid(uid: string, query?: any) { + return this.officeFoldersRepository.findOneByUid(uid, query); + } +} diff --git a/src/services/super-admin/OfficesService/OfficesService.ts b/src/services/super-admin/OfficesService/OfficesService.ts new file mode 100644 index 00000000..4de766be --- /dev/null +++ b/src/services/super-admin/OfficesService/OfficesService.ts @@ -0,0 +1,44 @@ +import { Offices } from "@prisma/client"; +import OfficesRepository from "@Repositories/OfficesRepository"; +import BaseService from "@Services/BaseService"; +import { Office as OfficeRessource } from "le-coffre-resources/dist/SuperAdmin"; +import { Service } from "typedi"; + +@Service() +export default class OfficesService extends BaseService { + constructor(private officeRepository: OfficesRepository) { + super(); + } + + /** + * @description : Get all offices + * @throws {Error} If offices cannot be get + */ + public async get(query: any): Promise { + return this.officeRepository.findMany(query); + } + + /** + * @description : Create a new office + * @throws {Error} If office cannot be created + */ + public async create(officeEntity: OfficeRessource): Promise { + return this.officeRepository.create(officeEntity); + } + + /** + * @description : Modify an office + * @throws {Error} If office cannot be modified + */ + public async update(uid: string, officeEntity: OfficeRessource): Promise { + return this.officeRepository.update(uid, officeEntity); + } + + /** + * @description : Get a office by uid + * @throws {Error} If office cannot be get + */ + public async getByUid(uid: string, query?: any): Promise { + return this.officeRepository.findOneByUid(uid, query); + } +} diff --git a/src/services/super-admin/UsersService/UsersService.ts b/src/services/super-admin/UsersService/UsersService.ts new file mode 100644 index 00000000..1e9c4445 --- /dev/null +++ b/src/services/super-admin/UsersService/UsersService.ts @@ -0,0 +1,45 @@ +import BaseService from "@Services/BaseService"; +import "reflect-metadata"; +import { Service } from "typedi"; +import UsersRepository from "@Repositories/UsersRepository"; +import User from "le-coffre-resources/dist/SuperAdmin"; +import { Users } from "@prisma/client"; + +@Service() +export default class UsersService extends BaseService { + constructor(private userRepository: UsersRepository) { + super(); + } + + /** + * @description : Get all users + * @throws {Error} If users cannot be get + */ + public get(query: any): Promise { + return this.userRepository.findMany(query); + } + + /** + * @description : Create a user + * @throws {Error} If user couldn't be created + */ + public create(userEntity: User): Promise { + return this.userRepository.create(userEntity); + } + + /** + * @description : Modify a user + * @throws {Error} If user modification failed + */ + public update(uid: string, userEntity: User): Promise { + return this.userRepository.update(uid, userEntity); + } + + /** + * @description : Get a user by uid + * @throws {Error} If user cannot be get by uid + */ + public getByUid(uid: string, query?: any): Promise { + return this.userRepository.findOneByUid(uid, query); + } +} diff --git a/src/test/config/Init.ts b/src/test/config/Init.ts new file mode 100644 index 00000000..8be002d5 --- /dev/null +++ b/src/test/config/Init.ts @@ -0,0 +1,121 @@ +import { Customers, DeedTypes, DocumentTypes, ECivility, ECustomerStatus, Offices, PrismaClient, Users } from "@prisma/client"; +import User, { Customer, DeedType, DocumentType, Office } from "le-coffre-resources/dist/SuperAdmin"; + +const prisma = new PrismaClient(); + +export const initOffice = (office: Office): Promise => { + return prisma.offices.create({ + data: { + idNot: office.idNot, + name: office.name, + crpcen: office.crpcen, + address: { + create: { + address: office.address.address, + zip_code: office.address.zip_code, + city: office.address.city, + }, + }, + }, + }); +}; + +export const initDocumentType = (documentType: DocumentType, office: Office): Promise => { + return prisma.documentTypes.create({ + data: { + name: documentType.name, + public_description: documentType.public_description, + private_description: documentType.private_description, + archived_at: null, + office_uid: office.uid!, + }, + }); +}; + +export const initDeedType = (deedType: DeedType, office: Office, documentTypes?: string[]): Promise => { + return prisma.deedTypes.create({ + data: { + name: deedType.name, + description: deedType.description, + archived_at: null, + office_uid: office.uid!, + deed_type_has_document_types: { + createMany: { + data: documentTypes!.map((documentType) => ({ + document_type_uid: documentType, + })), + skipDuplicates: true, + }, + }, + }, + }); +}; + +export const initCustomers = (customer: Customer): Promise => { + return prisma.customers.create({ + data: { + status: ECustomerStatus.PENDING, + contact: { + create: { + first_name: customer.contact.first_name, + last_name: customer.contact.last_name, + email: customer.contact.email, + phone_number: customer.contact.phone_number, + cell_phone_number: customer.contact?.cell_phone_number, + civility: ECivility[customer.contact.civility as keyof typeof ECivility], + address: { + create: { + address: customer.contact.address!.address, + zip_code: customer.contact.address!.zip_code, + city: customer.contact.address!.city, + }, + }, + }, + }, + }, + }); +}; + +export const initUsers = (user: User): Promise => { + return prisma.users.create({ + data: { + idNot: user.idNot, + office_membership: { + connectOrCreate: { + where: { + idNot: user.office_membership.idNot, + }, + create: { + idNot: user.office_membership.idNot, + name: user.office_membership.name, + crpcen: user.office_membership.crpcen, + address: { + create: { + address: user.office_membership.address.address, + zip_code: user.office_membership.address.zip_code, + city: user.office_membership.address.city, + }, + }, + }, + }, + }, + contact: { + create: { + first_name: user.contact.first_name, + last_name: user.contact.last_name, + email: user.contact.email, + phone_number: user.contact.phone_number, + cell_phone_number: user.contact.cell_phone_number, + civility: ECivility[user.contact.civility as keyof typeof ECivility], + address: { + create: { + address: user.contact.address!.address, + zip_code: user.contact.address!.zip_code, + city: user.contact.address!.city, + }, + }, + }, + }, + }, + }); +}; diff --git a/src/test/config/MockedData.ts b/src/test/config/MockedData.ts new file mode 100644 index 00000000..051fe912 --- /dev/null +++ b/src/test/config/MockedData.ts @@ -0,0 +1,242 @@ +import { EOfficeStatus } from "le-coffre-resources/dist/Customer/Office"; +import User, { Address, Contact, Office, DeedType, DocumentType, Customer, OfficeFolder, Deed } from "le-coffre-resources/dist/SuperAdmin"; + +export const userAddress: Address = { + address: "1 avenue des champs élysées", + zip_code: 75008, + city: "paris", + created_at: null, + updated_at: null, +}; + +export const userAddress_: Address = { + address: "1 rue Victor Hugo", + zip_code: 75001, + city: "paris", + created_at: null, + updated_at: null, +}; + +export const userContact: Contact = { + first_name: "Philippe", + last_name: "le Bel", + address: userAddress, + email: "philippe.lebel@notaires.fr", + phone_number: "+33101020304", + cell_phone_number: "+33605060708", + civility: "MALE", + created_at: null, + updated_at: null, +}; + +export const userContact_: Contact = { + first_name: "Saint", + last_name: "Louise", + address: userAddress_, + email: "saint.louise@notaires.fr", + phone_number: "+33105060708", + cell_phone_number: "+33601020304", + civility: "FEMALE", + created_at: null, + updated_at: null, +}; + +export const customerContact: Contact = { + first_name: "John", + last_name: "Doe", + address: userAddress, + email: "john.doe@customer.fr", + phone_number: "+3313847505", + cell_phone_number: "+3313847505", + civility: "MALE", + created_at: null, + updated_at: null, +}; + +export const customerContact_: Contact = { + first_name: "Jocelyne", + last_name: "Doe", + address: userAddress, + email: "jocelyne.doe@customer.fr", + phone_number: "+331384894505", + cell_phone_number: "+331384894505", + civility: "FEMALE", + created_at: null, + updated_at: null, +}; + +export const officeAddress: Address = { + address: "1 rue Rivoli", + zip_code: 75001, + city: "paris", + created_at: null, + updated_at: null, +}; + +export const officeAddress_: Address = { + address: "1 rue de la paix", + zip_code: 75008, + city: "paris", + created_at: null, + updated_at: null, +}; + +export const office: Office = { + idNot: "123456789", + name: "first office", + crpcen: "0123456789CRPCEN", + office_status: EOfficeStatus.ACTIVATED, + address: officeAddress, + created_at: null, + updated_at: null, +}; + +export const office_: Office = { + idNot: "789101112", + name: "second office", + crpcen: "987654321CRPCEN", + office_status: EOfficeStatus.DESACTIVATED, + address: officeAddress_, + created_at: null, + updated_at: null, +}; + +export const user: User = { + idNot: "123456_123456789", + contact: userContact, + office_membership: office, + created_at: null, + updated_at: null, +}; + +export const user_: User = { + idNot: "654321_789101112", + contact: userContact_, + office_membership: office_, + created_at: null, + updated_at: null, +}; + +export const documentType: DocumentType = { + name: "Identity card", + public_description: "your ID card delivered by your country of residence", + private_description: "verify if this ID card is legit", + archived_at: null, + office: office, + created_at: null, + updated_at: null, +}; + +export const documentType_: DocumentType = { + name: "Electricity bill", + public_description: "an electricity bill payed within the last 3 months", + private_description: "verify if this electricity company is legit", + archived_at: null, + office: office, + created_at: null, + updated_at: null, +}; + +export const deedType: DeedType = { + name: "Wedding", + description: "we assume wedding involve two people", + archived_at: null, + office: office, + created_at: null, + updated_at: null, + deed_type_has_document_types: [ + { + document_type: documentType, + deed_type: new DeedType(), + created_at: null, + updated_at: null, + }, + ], +}; + +export const deedType_: DeedType = { + name: "Inheritance", + description: "we assume inheritance involve two people", + archived_at: null, + office: office_, + created_at: null, + updated_at: null, +}; + +export const deed: Deed = { + deed_type: deedType, + created_at: null, + updated_at: null, +}; + +export const deed_: Deed = { + deed_type: deedType_, + created_at: null, + updated_at: null, +}; + +export const customer: Customer = { + contact: customerContact, + status: "PENDING", + created_at: null, + updated_at: null, +}; + +export const customer_: Customer = { + contact: customerContact_, + status: "ERRONED", + created_at: null, + updated_at: null, +}; + +export const officeFolder: OfficeFolder = { + name: "Dossier 1234567", + folder_number: "1234567", + description: "Dossier de mr Dupont", + archived_description: null, + status: "ARCHIVED", + deed: deed, + office: office, + office_folder_has_customers: [ + { + customer: customer, + office_folder: new OfficeFolder(), + created_at: null, + updated_at: null, + }, + { + customer: customer_, + office_folder: new OfficeFolder(), + created_at: null, + updated_at: null, + }, + ], + office_folder_has_stakeholder: [ + { + user_stakeholder: user, + office_folder: new OfficeFolder(), + created_at: null, + updated_at: null, + }, + { + user_stakeholder: user_, + office_folder: new OfficeFolder(), + created_at: null, + updated_at: null, + }, + ], + created_at: null, + updated_at: null, +}; + +export const officeFolder_: OfficeFolder = { + name: "Dossier 89101112", + folder_number: "89101112", + description: "Dossier de mme Dutunnel", + archived_description: null, + status: "LIVE", + deed: deed_, + office: office_, + created_at: null, + updated_at: null, +}; diff --git a/src/test/services/super-admin/CustomersService.test.ts b/src/test/services/super-admin/CustomersService.test.ts new file mode 100644 index 00000000..1ebccd55 --- /dev/null +++ b/src/test/services/super-admin/CustomersService.test.ts @@ -0,0 +1,166 @@ +import "module-alias/register"; +import "reflect-metadata"; +import { Customer } from "le-coffre-resources/dist/SuperAdmin"; +import CustomersService from "@Services/super-admin/CustomersService/CustomersService"; +import { PrismaClient } from "@prisma/client"; +import { customer, customerContact, customerContact_, customer_ } from "@Test/config/MockedData"; +import Container from "typedi"; +import CustomersRepository from "@Repositories/CustomersRepository"; + +const prisma = new PrismaClient(); + +const CustomersServiceTest = new CustomersService(Container.get(CustomersRepository)); + +afterAll(async () => { + /* + * Clean database after all tests execution. + * Due to cascade deletion, if addresses are deleted, all items following tables are dropped: contacts, customers, offices + */ + const deleteAddresses = prisma.addresses.deleteMany(); + await prisma.$transaction([deleteAddresses]); + await prisma.$disconnect(); +}); + +describe("test create function", () => { + it("should create a new customer", async () => { + const customerCreated = await CustomersServiceTest.create(customer); + + expect(customerCreated?.status).toEqual("PENDING"); + + // verify if customer contact is created in db + const contactCreated = await prisma.contacts.findUnique({ where: { uid: customerCreated.contact_uid } }); + expect(contactCreated?.first_name).toEqual(customer.contact.first_name); + expect(contactCreated?.last_name).toEqual(customer.contact.last_name); + expect(contactCreated?.cell_phone_number).toEqual(customer.contact.cell_phone_number); + expect(contactCreated?.phone_number).toEqual(customer.contact.phone_number); + expect(contactCreated?.civility).toEqual(customer.contact.civility); + expect(contactCreated?.email).toEqual(customer.contact.email); + + // verify if customer address is created in db + const addressForContactCreated = await prisma.addresses.findUnique({ where: { uid: contactCreated?.address_uid } }); + expect(addressForContactCreated?.address).toEqual(customer.contact.address?.address); + expect(addressForContactCreated?.zip_code).toEqual(customer.contact.address?.zip_code); + expect(addressForContactCreated?.city).toEqual(customer.contact.address?.city); + }); + + it("should not create an customer already created", async () => { + // try to create the same customer + async function duplicateCustomer() { + await CustomersServiceTest.create(customer); + } + await expect(duplicateCustomer).rejects.toThrow(); + }); + + it("should not create an new customer with an email already created", async () => { + let newCustomer: Customer = JSON.parse(JSON.stringify(customer_)); + newCustomer.contact.email = customerContact.email; + + // try to create a new customer with already used email + async function createCustomerWithDuplicateEmail() { + await CustomersServiceTest.create(newCustomer); + } + await expect(createCustomerWithDuplicateEmail).rejects.toThrow(); + }); + + it("should not create an customer with an phone number already created", async () => { + let newCustomer: Customer = JSON.parse(JSON.stringify(customer_)); + newCustomer.contact.cell_phone_number = customerContact.cell_phone_number; + + // try to create a new customer with already used cellphone number + async function duplicateCustomer() { + await CustomersServiceTest.create(newCustomer); + } + await expect(duplicateCustomer).rejects.toThrow(); + }); + + it("should create an new customer if unique attributes differ from existing customers", async () => { + let newCustomer: Customer = JSON.parse(JSON.stringify(customer)); + newCustomer.contact.email = customerContact_.email; + newCustomer.contact.cell_phone_number = customerContact_.cell_phone_number; + + const customerCreated = await CustomersServiceTest.create(newCustomer); + + expect(customerCreated?.status).toEqual("PENDING"); + + // verify if customer_ contact is created in db + const contactCreated = await prisma.contacts.findUnique({ where: { uid: customerCreated.contact_uid } }); + expect(contactCreated?.first_name).toEqual(customer.contact.first_name); + expect(contactCreated?.last_name).toEqual(customer.contact.last_name); + expect(contactCreated?.cell_phone_number).toEqual(customer_.contact.cell_phone_number); + expect(contactCreated?.phone_number).toEqual(customer.contact.phone_number); + expect(contactCreated?.civility).toEqual(customer.contact.civility); + expect(contactCreated?.email).toEqual(customer_.contact.email); + + // verify if customer_ address is created in db + const addressForContactCreated = await prisma.addresses.findUnique({ where: { uid: contactCreated?.address_uid } }); + expect(addressForContactCreated?.address).toEqual(customer.contact.address?.address); + expect(addressForContactCreated?.zip_code).toEqual(customer.contact.address?.zip_code); + expect(addressForContactCreated?.city).toEqual(customer.contact.address?.city); + }); +}); + +describe("test update function", () => { + it("should update an customer's data", async () => { + const customerCreated = await prisma.customers.findFirstOrThrow({ where: { contact: { email: customer_.contact.email } } }); + + // update the last customer created with his own new office and own contact + const updatedCustomer = await CustomersServiceTest.update(customerCreated.uid, customer_); + + expect(updatedCustomer?.status).toEqual("ERRONED"); + + // verify if customer_ contact is created in db + const existingContact = await prisma.contacts.findUnique({ where: { uid: updatedCustomer.contact_uid } }); + expect(existingContact?.first_name).toEqual(customer_.contact.first_name); + expect(existingContact?.last_name).toEqual(customer_.contact.last_name); + expect(existingContact?.cell_phone_number).toEqual(customer_.contact.cell_phone_number); + expect(existingContact?.phone_number).toEqual(customer_.contact.phone_number); + expect(existingContact?.civility).toEqual(customer_.contact.civility); + expect(existingContact?.email).toEqual(customer_.contact.email); + + // verify if customer_ address is created in db + const addressForExistingContact = await prisma.addresses.findUnique({ where: { uid: existingContact?.address_uid } }); + expect(addressForExistingContact?.address).toEqual(customer_.contact.address?.address); + expect(addressForExistingContact?.zip_code).toEqual(customer_.contact.address?.zip_code); + expect(addressForExistingContact?.city).toEqual(customer_.contact.address?.city); + }); + + it("should not update an customer with an email already used", async () => { + const customerUid = (await prisma.customers.findFirstOrThrow({ where: { contact: { email: customer_.contact.email } } })).uid; + let updatedCustomer: Customer = JSON.parse(JSON.stringify(customer_)); + updatedCustomer.contact.email = customerContact.email; + + // try to create a new customer with already used email + async function updateCustomerWithDuplicateEmail() { + await CustomersServiceTest.update(customerUid, updatedCustomer); + } + await expect(updateCustomerWithDuplicateEmail).rejects.toThrow(); + }); + + it("should not update an customer with an phone number already used", async () => { + const customerUid = (await prisma.customers.findFirstOrThrow({ where: { contact: { email: customer_.contact.email } } })).uid; + let updatedCustomer: Customer = JSON.parse(JSON.stringify(customer_)); + updatedCustomer.contact.cell_phone_number = customerContact.cell_phone_number; + + // try to create a new customer with already used email + async function updateCustomerWithDuplicateEmail() { + await CustomersServiceTest.update(customerUid, updatedCustomer); + } + await expect(updateCustomerWithDuplicateEmail).rejects.toThrow(); + }); +}); + +describe("test get function", () => { + it("should return an array of Customers", async () => { + const req = {} + const customers = await CustomersServiceTest.get(req); + + // verify result typing + expect(customers).toBeInstanceOf(Array); + expect(customers.length).toEqual(2); + + // verify result content + const customersCreated = await prisma.customers.findMany(); + expect(customers).toContainEqual(customersCreated[0]); + expect(customers).toContainEqual(customersCreated[1]); + }); +}); diff --git a/src/test/services/super-admin/DeedService.test.ts b/src/test/services/super-admin/DeedService.test.ts new file mode 100644 index 00000000..58511ebb --- /dev/null +++ b/src/test/services/super-admin/DeedService.test.ts @@ -0,0 +1,179 @@ +import "module-alias/register"; +import "reflect-metadata"; +import { Deed } from "le-coffre-resources/dist/SuperAdmin"; +import DeedService from "@Services/super-admin/DeedsService/DeedsService"; +import { PrismaClient } from "prisma/prisma-client"; +import { deed, deedType, documentType, documentType_, office } from "@Test/config/MockedData"; +import DeedsRepository from "@Repositories/DeedsRepository"; +import Container from "typedi"; +import { initDeedType, initDocumentType, initOffice } from "@Test/config/Init"; + +const prisma = new PrismaClient(); + +const DeedServiceTest = new DeedService(Container.get(DeedsRepository)); + +beforeAll(async () => { + office.uid = (await initOffice(office)).uid; + documentType.uid = (await initDocumentType(documentType, office)).uid; + documentType_.uid = (await initDocumentType(documentType_, office)).uid; + deedType.uid = (await initDeedType(deedType, office, [documentType.uid])).uid; +}); + +afterAll(async () => { + const deleteDeedTypes = prisma.deedTypes.deleteMany(); + const deleteOffices = prisma.offices.deleteMany(); + await prisma.$transaction([deleteDeedTypes, deleteOffices]); + await prisma.$disconnect(); +}); + +describe("test create function", () => { + it("should not create a new deed if deed type is unknown", async () => { + let deedWithoutDeedTypeUid: Deed = JSON.parse(JSON.stringify(deed)); + deedWithoutDeedTypeUid.deed_type.uid = "random uid"; + // try to create a new deed with unknown deed type + async function createDeedWithUnknownDeedType() { + await DeedServiceTest.create(deedWithoutDeedTypeUid); + } + await expect(createDeedWithUnknownDeedType).rejects.toThrow(); + }); + + it("should create a new deed based on existing deed type", async () => { + let deedWithDeedTypeUid: Deed = JSON.parse(JSON.stringify(deed)); + deedWithDeedTypeUid.deed_type.uid = deedType.uid; + const deedCreated = await DeedServiceTest.create(deedWithDeedTypeUid); + + expect(deedCreated.deed_type_uid).toEqual(deedType.uid); + }); + + it("should have by default the same document types as its deed type ", async () => { + const deedWithDocumentTypes = await prisma.deeds.findFirstOrThrow({ include: { deed_has_document_types: true } }); + expect(deedWithDocumentTypes.deed_has_document_types.length).toEqual(1); + expect(deedWithDocumentTypes.deed_has_document_types[0]?.document_type_uid).toEqual(documentType.uid); + }); + + it("should create a the same deed based on existing deed type", async () => { + let deedWithDeedTypeUid: Deed = JSON.parse(JSON.stringify(deed)); + deedWithDeedTypeUid.deed_type.uid = deedType.uid; + const deedCreated = await DeedServiceTest.create(deedWithDeedTypeUid); + + expect(deedCreated.deed_type_uid).toEqual(deedType.uid); + }); + + it("should not create a new deed based on archivated deed type", async () => { + let deedArchivated: Deed = JSON.parse(JSON.stringify(deed)); + deedArchivated.deed_type.uid = deedType.uid; + + await prisma.deedTypes.update({ + where: { uid: deedType.uid }, + data: { + archived_at: new Date(Date.now()), + }, + }); + + // try to create a new deed with archivated deed type + async function createDeedWithArchivatedDeedType() { + await DeedServiceTest.create(deedArchivated); + } + await expect(createDeedWithArchivatedDeedType).rejects.toThrow("deed type is archived"); + }); +}); + +describe("test update function", () => { + it("should add document types to a deed", async () => { + const deedUid = (await prisma.deeds.findFirstOrThrow({ where: { deed_type_uid: deedType.uid } })).uid; + let deedToUpdate: Deed = JSON.parse(JSON.stringify(deed)); + + deedToUpdate.deed_has_document_types = [ + { + document_type: documentType, + deed: new Deed(), + created_at: null, + updated_at: null, + }, + { + document_type: documentType_, + deed: new Deed(), + created_at: null, + updated_at: null, + }, + ]; + + await DeedServiceTest.update(deedUid, deedToUpdate); + + const deedUpdated = await prisma.deeds.findFirstOrThrow({ + where: { + uid: deedUid, + }, + include: { + deed_has_document_types: true, + }, + }); + expect(deedUpdated.deed_has_document_types.length).toEqual(2); + }); + + it("should not add document types to a deed type that already has those document types ", async () => { + const deedUid = (await prisma.deeds.findFirstOrThrow({ where: { deed_type_uid: deedType.uid } })).uid; + let deedToUpdate: Deed = JSON.parse(JSON.stringify(deed)); + + deedToUpdate.deed_has_document_types = [ + { + document_type: documentType, + deed: new Deed(), + created_at: null, + updated_at: null, + }, + { + document_type: documentType_, + deed: new Deed(), + created_at: null, + updated_at: null, + }, + ]; + + await DeedServiceTest.update(deedUid, deedToUpdate); + + const deedUpdated = await prisma.deeds.findFirstOrThrow({ + where: { + uid: deedUid, + }, + include: { + deed_has_document_types: true, + }, + }); + expect(deedUpdated.deed_has_document_types.length).toEqual(2); + }); + + it("should delete document types from a deed", async () => { + const deedUid = (await prisma.deeds.findFirstOrThrow({ where: { deed_type_uid: deedType.uid } })).uid; + let deedToUpdate: Deed = JSON.parse(JSON.stringify(deed)); + + // set relation between deed and document types empty + deedToUpdate.deed_has_document_types = []; + + await DeedServiceTest.update(deedUid, deedToUpdate); + + const deedUpdated = await prisma.deeds.findFirstOrThrow({ + where: { + uid: deedUid, + }, + include: { + deed_has_document_types: true, + }, + }); + expect(deedUpdated.deed_has_document_types.length).toEqual(0); + }); +}); + +describe("test get function", () => { + it("should return an array of Deeds", async () => { + const deeds = await DeedServiceTest.get({}); + + // verify result typing + expect(deeds).toBeInstanceOf(Array); + expect(deeds.length).toEqual(2); + + // verify result content + expect(deeds[0]?.deed_type_uid).toEqual(deedType.uid); + expect(deeds[1]?.deed_type_uid).toEqual(deedType.uid); + }); +}); diff --git a/src/test/services/super-admin/DeedTypesService.test.ts b/src/test/services/super-admin/DeedTypesService.test.ts new file mode 100644 index 00000000..5cf11f7f --- /dev/null +++ b/src/test/services/super-admin/DeedTypesService.test.ts @@ -0,0 +1,323 @@ +import "module-alias/register"; +import "reflect-metadata"; +import { DeedType } from "le-coffre-resources/dist/SuperAdmin"; +import DeedTypeService from "@Services/super-admin/DeedTypesService/DeedTypesService"; +import { PrismaClient } from "prisma/prisma-client"; +import { deedType, deedType_, documentType, documentType_, office, office_ } from "@Test/config/MockedData"; +import DeedTypesRepository from "@Repositories/DeedTypesRepository"; +import Container from "typedi"; +import { initDocumentType, initOffice } from "@Test/config/Init"; + +const prisma = new PrismaClient(); + +const DeedTypeServiceTest = new DeedTypeService(Container.get(DeedTypesRepository)); + +beforeAll(async () => { + office.uid = (await initOffice(office)).uid; + office_.uid = (await initOffice(office_)).uid; + documentType.uid = (await initDocumentType(documentType, office)).uid; + documentType_.uid = (await initDocumentType(documentType_, office)).uid; +}); + +afterAll(async () => { + const deleteDeedTypes = prisma.deedTypes.deleteMany(); + const deleteOffices = prisma.offices.deleteMany(); + await prisma.$transaction([deleteDeedTypes, deleteOffices]); + await prisma.$disconnect(); +}); + +describe("test create function", () => { + it("should not create a new deed type if office is unknown", async () => { + let deedTypeWithoutOfficeUid: DeedType = JSON.parse(JSON.stringify(deedType)); + deedTypeWithoutOfficeUid.office.uid = "random uid"; + // try to create a new deed type with unknown office + async function createDeedTypeWithUnknownOffice() { + await DeedTypeServiceTest.create(deedTypeWithoutOfficeUid); + } + await expect(createDeedTypeWithUnknownOffice).rejects.toThrow(); + }); + + it("should create a new deed type", async () => { + const deedTypeCreated = await DeedTypeServiceTest.create(deedType); + + expect(deedTypeCreated.name).toEqual(deedType.name); + expect(deedTypeCreated.description).toEqual(deedType.description); + expect(deedTypeCreated.archived_at).toBeNull(); + expect(deedTypeCreated.office_uid).toEqual(office.uid); + }); + + it("should not create a new deed type with a name already used for a given office", async () => { + let deedTypeWithSameNameAndOffice: DeedType = JSON.parse(JSON.stringify(deedType_)); + deedTypeWithSameNameAndOffice.office = office; + deedTypeWithSameNameAndOffice.name = deedType.name; + + async function createDeedTypeWithSameNameAndOffice() { + await DeedTypeServiceTest.create(deedTypeWithSameNameAndOffice); + } + await expect(createDeedTypeWithSameNameAndOffice).rejects.toThrow(); + }); + + it("should create the same deed type for a different office", async () => { + let deedTypeDuplicatedForNewOffice: DeedType = JSON.parse(JSON.stringify(deedType)); + deedTypeDuplicatedForNewOffice.office = office_; + + const deedTypeCreated = await DeedTypeServiceTest.create(deedTypeDuplicatedForNewOffice); + + expect(deedTypeCreated.name).toEqual(deedType.name); + expect(deedTypeCreated.description).toEqual(deedType.description); + expect(deedTypeCreated.archived_at).toBeNull(); + expect(deedTypeCreated.office_uid).toEqual(office_.uid); + }); + + it("should create the a new deed type version with a different name for a given office", async () => { + let deedTypeWithSameDescription: DeedType = JSON.parse(JSON.stringify(deedType)); + deedTypeWithSameDescription.name = deedType_.name; + + const deedTypeCreated = await DeedTypeServiceTest.create(deedTypeWithSameDescription); + + expect(deedTypeCreated.name).toEqual(deedType_.name); + expect(deedTypeCreated.description).toEqual(deedType.description); + expect(deedTypeCreated.archived_at).toBeNull(); + expect(deedTypeCreated.office_uid).toEqual(office.uid); + }); +}); + +describe("test update function", () => { + it("should update a deed type data", async () => { + const deedTypeCreated = await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType_.name, office_uid: office.uid } }); + + expect(deedTypeCreated.name).toEqual(deedType_.name); + expect(deedTypeCreated.description).toEqual(deedType.description); + expect(deedTypeCreated.archived_at).toBeNull(); + expect(deedTypeCreated.office_uid).toEqual(deedType.office.uid); + + let deedTypeWithNewDescription: DeedType = JSON.parse(JSON.stringify(deedType_)); + deedTypeWithNewDescription.office = office; + + // update the last deed type created with his the right description + const deedTypeUpdated = await DeedTypeServiceTest.update(deedTypeCreated.uid, deedTypeWithNewDescription); + + expect(deedTypeUpdated.name).toEqual(deedType_.name); + expect(deedTypeUpdated.description).toEqual(deedType_.description); + expect(deedTypeUpdated.archived_at).toBeNull(); + expect(deedTypeUpdated.office_uid).toEqual(deedType.office.uid); + }); + + it("should not update a deed type name with an already used name for given office", async () => { + const deedTypeUid = (await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType_.name, office_uid: office.uid } })).uid; + let deedTypeWithSameNameAndOffice: DeedType = JSON.parse(JSON.stringify(deedType_)); + deedTypeWithSameNameAndOffice.office.uid = office.uid; + deedTypeWithSameNameAndOffice.name = deedType.name; + + // update the last deed type created with his the right description + async function updateDocumentTypeWithSameName() { + await DeedTypeServiceTest.update(deedTypeUid, deedTypeWithSameNameAndOffice); + } + await expect(updateDocumentTypeWithSameName).rejects.toThrow(); + }); + + it("should not update a deed type office membership if the office already have this document type", async () => { + const deedTypeUid = (await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType_.name, office_uid: office.uid } })).uid; + let deedTypeWithSameNameAndOffice: DeedType = JSON.parse(JSON.stringify(deedType_)); + deedTypeWithSameNameAndOffice.office.uid = office.uid; + deedTypeWithSameNameAndOffice.name = deedType.name; + + // try to duplicate deed type in a given office + async function updateDocumentTypeWithduplicatedName() { + await DeedTypeServiceTest.update(deedTypeUid, deedTypeWithSameNameAndOffice); + } + await expect(updateDocumentTypeWithduplicatedName).rejects.toThrow(); + }); + + it("should update a deed type office membership", async () => { + const deedTypeCreated = await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType_.name, office_uid: office.uid } }); + + expect(deedTypeCreated.name).toEqual(deedType_.name); + expect(deedTypeCreated.description).toEqual(deedType_.description); + expect(deedTypeCreated.archived_at).toBeNull(); + expect(deedTypeCreated.office_uid).toEqual(office.uid); + + // update the last deed type updated with a new office membership + const deedTypeUpdated = await DeedTypeServiceTest.update(deedTypeCreated.uid, deedType_); + + expect(deedTypeUpdated.name).toEqual(deedType_.name); + expect(deedTypeUpdated.description).toEqual(deedType_.description); + expect(deedTypeUpdated.archived_at).toBeNull(); + expect(deedTypeUpdated.office_uid).toEqual(deedType_.office.uid); + }); + + it("should archivate a deed type", async () => { + const deedTypeCreated = await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType_.name, office_uid: office_.uid } }); + + expect(deedTypeCreated.name).toEqual(deedType_.name); + expect(deedTypeCreated.description).toEqual(deedType_.description); + expect(deedTypeCreated.archived_at).toBeNull(); + expect(deedTypeCreated.office_uid).toEqual(deedType_.office.uid); + + let deedTypeArchivated: DeedType = JSON.parse(JSON.stringify(deedType_)); + const currentDate = new Date(Date.now()); + deedTypeArchivated.archived_at = new Date(Date.now()); + + // archivate a deed type by giving a non null date for archivated_at attribute + const deedTypeUpdated = await DeedTypeServiceTest.update(deedTypeCreated.uid, deedTypeArchivated); + + expect(deedTypeUpdated.name).toEqual(deedType_.name); + expect(deedTypeUpdated.description).toEqual(deedType_.description); + expect(deedTypeUpdated.archived_at).toEqual(currentDate); + expect(deedTypeUpdated.office_uid).toEqual(office_.uid); + }); + + it("should unarchivate a deed type", async () => { + const deedTypeCreated = await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType_.name, office_uid: office_.uid } }); + + expect(deedTypeCreated.name).toEqual(deedType_.name); + expect(deedTypeCreated.description).toEqual(deedType_.description); + expect(deedTypeCreated.archived_at).not.toBeNull(); + expect(deedTypeCreated.office_uid).toEqual(deedType_.office.uid); + + // unarchivate a deed type by giving a null date for archivated_at attribute + const deedTypeUpdated = await DeedTypeServiceTest.update(deedTypeCreated.uid, deedType_); + + expect(deedTypeUpdated.name).toEqual(deedType_.name); + expect(deedTypeUpdated.description).toEqual(deedType_.description); + expect(deedTypeUpdated.archived_at).toBeNull(); + expect(deedTypeUpdated.office_uid).toEqual(office_.uid); + }); + + it("should add document types to a deed type", async () => { + const deedTypeUid = (await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType.name, office_uid: office.uid } })).uid; + let deedTypeToUpdate: DeedType = JSON.parse(JSON.stringify(deedType)); + + deedTypeToUpdate.deed_type_has_document_types = [ + { + document_type: documentType, + deed_type: new DeedType(), + created_at: null, + updated_at: null, + }, + { + document_type: documentType_, + deed_type: new DeedType(), + created_at: null, + updated_at: null, + }, + ]; + + await DeedTypeServiceTest.update(deedTypeUid, deedTypeToUpdate); + + const deedTypeUpdated = await prisma.deedTypes.findFirstOrThrow({ + where: { + uid: deedTypeUid, + }, + include: { + deed_type_has_document_types: true, + }, + }); + expect(deedTypeUpdated.deed_type_has_document_types.length).toEqual(2); + }); + + it("should not add document types to a deed type that already has those document types ", async () => { + const deedTypeUid = (await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType.name, office_uid: office.uid } })).uid; + let deedTypeToUpdate: DeedType = JSON.parse(JSON.stringify(deedType)); + + deedTypeToUpdate.deed_type_has_document_types = [ + { + document_type: documentType, + deed_type: new DeedType(), + created_at: null, + updated_at: null, + }, + { + document_type: documentType_, + deed_type: new DeedType(), + created_at: null, + updated_at: null, + }, + ]; + + await DeedTypeServiceTest.update(deedTypeUid, deedTypeToUpdate); + + const deedTypeUpdated = await prisma.deedTypes.findFirstOrThrow({ + where: { + uid: deedTypeUid, + }, + include: { + deed_type_has_document_types: true, + }, + }); + expect(deedTypeUpdated.deed_type_has_document_types.length).toEqual(2); + }); + + it("should delete document types from a deed", async () => { + const deedTypeUid = (await prisma.deedTypes.findFirstOrThrow({ where: { name: deedType.name, office_uid: office.uid } })).uid; + let deedTypeToUpdate: DeedType = JSON.parse(JSON.stringify(deedType)); + + // set relation between deed and document types empty + deedTypeToUpdate.deed_type_has_document_types = []; + + await DeedTypeServiceTest.update(deedTypeUid, deedTypeToUpdate); + + const deedTypeUpdated = await prisma.deedTypes.findFirstOrThrow({ + where: { + uid: deedTypeUid, + }, + include: { + deed_type_has_document_types: true, + }, + }); + expect(deedTypeUpdated.deed_type_has_document_types.length).toEqual(0); + }); +}); + +describe("test get function", () => { + it("should return an array of DeedTypes", async () => { + const deedTypes = await DeedTypeServiceTest.get({ orderBy: [{ name: "asc" }, { created_at: "asc" }] }); + + // verify result typing + expect(deedTypes).toBeInstanceOf(Array); + expect(deedTypes.length).toEqual(3); + + // verify result content + expect(deedTypes[0]?.name).toEqual(deedType_.name); + expect(deedTypes[0]?.description).toEqual(deedType_.description); + expect(deedTypes[0]?.archived_at).toBeNull(); + expect(deedTypes[0]?.office_uid).toEqual(office_.uid); + + expect(deedTypes[1]?.name).toEqual(deedType.name); + expect(deedTypes[1]?.description).toEqual(deedType.description); + expect(deedTypes[1]?.archived_at).toBeNull(); + expect(deedTypes[1]?.office_uid).toEqual(office.uid); + + expect(deedTypes[2]?.name).toEqual(deedType.name); + expect(deedTypes[2]?.description).toEqual(deedType.description); + expect(deedTypes[2]?.archived_at).toBeNull(); + expect(deedTypes[2]?.office_uid).toEqual(office_.uid); + }); + + it("should return an array of DeedTypes per offices", async () => { + const deedTypesForFirstOffice = await DeedTypeServiceTest.get({ where: { office: {uid: office.uid} }, orderBy: { name: "asc" } }); + + expect(deedTypesForFirstOffice.length).toEqual(1); + + // verify result content + expect(deedTypesForFirstOffice[0]?.name).toEqual(deedType.name); + expect(deedTypesForFirstOffice[0]?.description).toEqual(deedType.description); + expect(deedTypesForFirstOffice[0]?.archived_at).toBeNull(); + expect(deedTypesForFirstOffice[0]?.office_uid).toEqual(office.uid); + + const deedTypesForSecondOffice = await DeedTypeServiceTest.get({ where: { office: {uid: office_.uid} }, orderBy: { name: "asc" } }); + + expect(deedTypesForSecondOffice.length).toEqual(2); + + // verify result content + expect(deedTypesForSecondOffice[0]?.name).toEqual(deedType_.name); + expect(deedTypesForSecondOffice[0]?.description).toEqual(deedType_.description); + expect(deedTypesForSecondOffice[0]?.archived_at).toBeNull(); + expect(deedTypesForSecondOffice[0]?.office_uid).toEqual(office_.uid); + + expect(deedTypesForSecondOffice[1]?.name).toEqual(deedType.name); + expect(deedTypesForSecondOffice[1]?.description).toEqual(deedType.description); + expect(deedTypesForSecondOffice[1]?.archived_at).toBeNull(); + expect(deedTypesForSecondOffice[1]?.office_uid).toEqual(office_.uid); + }); +}); diff --git a/src/test/services/super-admin/DocumentTypesService.test.ts b/src/test/services/super-admin/DocumentTypesService.test.ts new file mode 100644 index 00000000..e8c88d17 --- /dev/null +++ b/src/test/services/super-admin/DocumentTypesService.test.ts @@ -0,0 +1,272 @@ +import "module-alias/register"; +import "reflect-metadata"; +import { DocumentType } from "le-coffre-resources/dist/SuperAdmin"; +import DocumentTypesService from "@Services/super-admin/DocumentTypesService/DocumentTypesService"; +import { PrismaClient } from "prisma/prisma-client"; +import { documentType, documentType_, office, office_ } from "@Test/config/MockedData"; +import DocumentTypesRepository from "@Repositories/DocumentTypesRepository"; +import Container from "typedi"; +import { initOffice } from "@Test/config/Init"; + +const prisma = new PrismaClient(); + +const DocumentTypesServiceTest = new DocumentTypesService(Container.get(DocumentTypesRepository)); + +beforeAll(async () => { + office.uid = (await initOffice(office)).uid; + office_.uid = (await initOffice(office_)).uid; +}); + +afterAll(async () => { + const deleteDocumentTypes = prisma.documentTypes.deleteMany(); + const deleteOffices = prisma.offices.deleteMany(); + await prisma.$transaction([deleteDocumentTypes, deleteOffices]); + await prisma.$disconnect(); +}); + +describe("test create function", () => { + it("should not create a new document type if office is unknown", async () => { + let documentTypeWithoutOfficeUid: DocumentType = JSON.parse(JSON.stringify(documentType)); + documentTypeWithoutOfficeUid.office.uid = "random uid"; + // try to create a new document type with unknown office + async function createDocumentTypeWithUnknownOffice() { + await DocumentTypesServiceTest.create(documentTypeWithoutOfficeUid); + } + await expect(createDocumentTypeWithUnknownOffice).rejects.toThrow(); + }); + + it("should create a new document type", async () => { + let documentTypeWithOfficeUid: DocumentType = JSON.parse(JSON.stringify(documentType)); + documentTypeWithOfficeUid.office.uid = office.uid; + const documentTypeCreated = await DocumentTypesServiceTest.create(documentTypeWithOfficeUid); + + expect(documentTypeCreated.name).toEqual(documentType.name); + expect(documentTypeCreated.public_description).toEqual(documentType.public_description); + expect(documentTypeCreated.private_description).toEqual(documentType.private_description); + expect(documentTypeCreated.archived_at).toBeNull(); + expect(documentTypeCreated.office_uid).toEqual(office.uid); + }); + + it("should not create a new document type with a name already used for a given office", async () => { + let documentTypeWithSameNameAndOffice: DocumentType = JSON.parse(JSON.stringify(documentType_)); + documentTypeWithSameNameAndOffice.office.uid = office.uid; + documentTypeWithSameNameAndOffice.name = documentType.name; + + async function createDocumentTypeWithSameNameAndOffice() { + await DocumentTypesServiceTest.create(documentTypeWithSameNameAndOffice); + } + await expect(createDocumentTypeWithSameNameAndOffice).rejects.toThrow(); + }); + + it("should create the same document type for a different office", async () => { + let documentTypeDuplicatedForNewOffice: DocumentType = JSON.parse(JSON.stringify(documentType)); + documentTypeDuplicatedForNewOffice.office.uid = office_.uid; + + const documentTypeCreated = await DocumentTypesServiceTest.create(documentTypeDuplicatedForNewOffice); + + expect(documentTypeCreated.name).toEqual(documentType.name); + expect(documentTypeCreated.public_description).toEqual(documentType.public_description); + expect(documentTypeCreated.private_description).toEqual(documentType.private_description); + expect(documentTypeCreated.archived_at).toBeNull(); + expect(documentTypeCreated.office_uid).toEqual(office_.uid); + }); + + it("should create a new document type version with a different name for a given office", async () => { + let documentTypeWithSameDescription: DocumentType = JSON.parse(JSON.stringify(documentType)); + documentTypeWithSameDescription.office.uid = office.uid; + documentTypeWithSameDescription.name = documentType_.name; + + const documentTypeCreated = await DocumentTypesServiceTest.create(documentTypeWithSameDescription); + + expect(documentTypeCreated.name).toEqual(documentType_.name); + expect(documentTypeCreated.public_description).toEqual(documentType.public_description); + expect(documentTypeCreated.private_description).toEqual(documentType.private_description); + expect(documentTypeCreated.archived_at).toBeNull(); + expect(documentTypeCreated.office_uid).toEqual(office.uid); + }); +}); + +describe("test update function", () => { + it("should update a document type", async () => { + const documentTypeCreated = await prisma.documentTypes.findFirstOrThrow({ + where: { name: documentType_.name, office_uid: office.uid }, + }); + expect(documentTypeCreated.name).toEqual(documentType_.name); + expect(documentTypeCreated.public_description).toEqual(documentType.public_description); + expect(documentTypeCreated.private_description).toEqual(documentType.private_description); + expect(documentTypeCreated.archived_at).toBeNull(); + expect(documentTypeCreated.office_uid).toEqual(office.uid); + + let documentTypeWithNewDescription: DocumentType = JSON.parse(JSON.stringify(documentType_)); + documentTypeWithNewDescription.office.uid = office.uid; + + // update the last document type created with his the right descriptions + const documentTypeUpdated = await DocumentTypesServiceTest.update(documentTypeCreated.uid, documentTypeWithNewDescription); + expect(documentTypeUpdated.name).toEqual(documentType_.name); + expect(documentTypeUpdated.public_description).toEqual(documentType_.public_description); + expect(documentTypeUpdated.private_description).toEqual(documentType_.private_description); + expect(documentTypeUpdated.archived_at).toBeNull(); + expect(documentTypeUpdated.office_uid).toEqual(office.uid); + }); + + it("should not update a document type name with an already used name for given office", async () => { + const documentTypeUid = ( + await prisma.documentTypes.findFirstOrThrow({ where: { name: documentType_.name, office_uid: office.uid } }) + ).uid; + let documentTypeWithSameNameAndOffice: DocumentType = JSON.parse(JSON.stringify(documentType_)); + documentTypeWithSameNameAndOffice.office.uid = office.uid; + documentTypeWithSameNameAndOffice.name = documentType.name; + + // update the last document type created with his the right description + async function updateDocumentTypeWithSameName() { + await DocumentTypesServiceTest.update(documentTypeUid, documentTypeWithSameNameAndOffice); + } + await expect(updateDocumentTypeWithSameName).rejects.toThrow(); + }); + + it("should not update a document type office membership if the office already has this document type", async () => { + const documentTypeUid = ( + await prisma.documentTypes.findFirstOrThrow({ where: { name: documentType_.name, office_uid: office.uid } }) + ).uid; + let documentTypeWithSameNameAndOffice: DocumentType = JSON.parse(JSON.stringify(documentType_)); + documentTypeWithSameNameAndOffice.office.uid = office.uid; + documentTypeWithSameNameAndOffice.name = documentType.name; + + // try to duplicate document type in a given office + async function updateDocumentTypeWithduplicatedName() { + await DocumentTypesServiceTest.update(documentTypeUid, documentTypeWithSameNameAndOffice); + } + await expect(updateDocumentTypeWithduplicatedName).rejects.toThrow(); + }); + + it("should update a document type office membership", async () => { + const documentTypeCreated = await prisma.documentTypes.findFirstOrThrow({ + where: { name: documentType_.name, office_uid: office.uid }, + }); + + expect(documentTypeCreated.name).toEqual(documentType_.name); + expect(documentTypeCreated.public_description).toEqual(documentType_.public_description); + expect(documentTypeCreated.private_description).toEqual(documentType_.private_description); + expect(documentTypeCreated.archived_at).toBeNull(); + expect(documentTypeCreated.office_uid).toEqual(office.uid); + + let documentTypeTransferedToNewOffice: DocumentType = JSON.parse(JSON.stringify(documentType_)); + documentTypeTransferedToNewOffice.office.uid = office_.uid; + + // update the last document type updated with a new office membership + const documentTypeUpdated = await DocumentTypesServiceTest.update(documentTypeCreated.uid, documentTypeTransferedToNewOffice); + + expect(documentTypeUpdated.name).toEqual(documentType_.name); + expect(documentTypeUpdated.public_description).toEqual(documentType_.public_description); + expect(documentTypeUpdated.private_description).toEqual(documentType_.private_description); + expect(documentTypeUpdated.archived_at).toBeNull(); + expect(documentTypeUpdated.office_uid).toEqual(office_.uid); + }); + + it("should archivate a document type", async () => { + const documentTypeCreated = await prisma.documentTypes.findFirstOrThrow({ + where: { name: documentType_.name, office_uid: office_.uid }, + }); + + expect(documentTypeCreated.name).toEqual(documentType_.name); + expect(documentTypeCreated.public_description).toEqual(documentType_.public_description); + expect(documentTypeCreated.private_description).toEqual(documentType_.private_description); + expect(documentTypeCreated.archived_at).toBeNull(); + expect(documentTypeCreated.office_uid).toEqual(office_.uid); + + let documentTypeArchivated: DocumentType = JSON.parse(JSON.stringify(documentType_)); + documentTypeArchivated.office.uid = office_.uid; + const currentDate = new Date(Date.now()); + documentTypeArchivated.archived_at = currentDate; + // archivate a document type by giving a non null date for archivated_at attribute + const documentTypeUpdated = await DocumentTypesServiceTest.update(documentTypeCreated.uid, documentTypeArchivated); + expect(documentTypeUpdated.name).toEqual(documentType_.name); + expect(documentTypeUpdated.public_description).toEqual(documentType_.public_description); + expect(documentTypeUpdated.private_description).toEqual(documentType_.private_description); + expect(documentTypeUpdated.archived_at).toEqual(currentDate); + expect(documentTypeUpdated.office_uid).toEqual(office_.uid); + }); + + it("should unarchivate a document type", async () => { + const documentTypeCreated = await prisma.documentTypes.findFirstOrThrow({ + where: { name: documentType_.name, office_uid: office_.uid }, + }); + + expect(documentTypeCreated.name).toEqual(documentType_.name); + expect(documentTypeCreated.public_description).toEqual(documentType_.public_description); + expect(documentTypeCreated.private_description).toEqual(documentType_.private_description); + expect(documentTypeCreated.archived_at).not.toBeNull(); + expect(documentTypeCreated.office_uid).toEqual(office_.uid); + + let documentTypeUnarchivated: DocumentType = JSON.parse(JSON.stringify(documentType_)); + documentTypeUnarchivated.office.uid = office_.uid; + + // unarchivate a document type by giving a null date for archivated_at attribute + const documentTypeUpdated = await DocumentTypesServiceTest.update(documentTypeCreated.uid, documentTypeUnarchivated); + + expect(documentTypeUpdated.name).toEqual(documentType_.name); + expect(documentTypeUpdated.public_description).toEqual(documentType_.public_description); + expect(documentTypeUpdated.private_description).toEqual(documentType_.private_description); + expect(documentTypeUpdated.archived_at).toBeNull(); + expect(documentTypeUpdated.office_uid).toEqual(office_.uid); + }); +}); + +describe("test get function", () => { + it("should return an array of DocumentTypes", async () => { + const documentTypes = await DocumentTypesServiceTest.get({ orderBy: [{ name: "asc" }, { created_at: "asc" }] }); + + // verify result typing + expect(documentTypes).toBeInstanceOf(Array); + expect(documentTypes.length).toEqual(3); + + // verify result content + expect(documentTypes[0]?.name).toEqual(documentType_.name); + expect(documentTypes[0]?.public_description).toEqual(documentType_.public_description); + expect(documentTypes[0]?.private_description).toEqual(documentType_.private_description); + expect(documentTypes[0]?.archived_at).toBeNull(); + expect(documentTypes[0]?.office_uid).toEqual(office_.uid); + + expect(documentTypes[1]?.name).toEqual(documentType.name); + expect(documentTypes[1]?.public_description).toEqual(documentType.public_description); + expect(documentTypes[1]?.private_description).toEqual(documentType.private_description); + expect(documentTypes[1]?.archived_at).toBeNull(); + expect(documentTypes[1]?.office_uid).toEqual(office.uid); + + expect(documentTypes[2]?.name).toEqual(documentType.name); + expect(documentTypes[2]?.public_description).toEqual(documentType.public_description); + expect(documentTypes[2]?.private_description).toEqual(documentType.private_description); + expect(documentTypes[2]?.archived_at).toBeNull(); + expect(documentTypes[2]?.office_uid).toEqual(office_.uid); + }); + + it("should return an array of DocumentTypes per offices", async () => { + const documentTypesForFirstOffice = await DocumentTypesServiceTest.get({ where: { office: {uid : office.uid }}, orderBy: { name: "asc" } }); + + expect(documentTypesForFirstOffice.length).toEqual(1); + + // verify result content + expect(documentTypesForFirstOffice[0]?.name).toEqual(documentType.name); + expect(documentTypesForFirstOffice[0]?.public_description).toEqual(documentType.public_description); + expect(documentTypesForFirstOffice[0]?.private_description).toEqual(documentType.private_description); + expect(documentTypesForFirstOffice[0]?.archived_at).toBeNull(); + expect(documentTypesForFirstOffice[0]?.office_uid).toEqual(office.uid); + + const documentTypesForSecondOffice = await DocumentTypesServiceTest.get({ where: { office: {uid : office_.uid }}, orderBy: { name: "asc" } }); + + expect(documentTypesForSecondOffice.length).toEqual(2); + + // verify result content + expect(documentTypesForSecondOffice[0]?.name).toEqual(documentType_.name); + expect(documentTypesForSecondOffice[0]?.public_description).toEqual(documentType_.public_description); + expect(documentTypesForSecondOffice[0]?.private_description).toEqual(documentType_.private_description); + expect(documentTypesForSecondOffice[0]?.archived_at).toBeNull(); + expect(documentTypesForSecondOffice[0]?.office_uid).toEqual(office_.uid); + + expect(documentTypesForSecondOffice[1]?.name).toEqual(documentType.name); + expect(documentTypesForSecondOffice[1]?.public_description).toEqual(documentType.public_description); + expect(documentTypesForSecondOffice[1]?.private_description).toEqual(documentType.private_description); + expect(documentTypesForSecondOffice[1]?.archived_at).toBeNull(); + expect(documentTypesForSecondOffice[1]?.office_uid).toEqual(office_.uid); + }); +}); diff --git a/src/test/services/super-admin/OfficeFolderService.test.ts b/src/test/services/super-admin/OfficeFolderService.test.ts new file mode 100644 index 00000000..6011f30e --- /dev/null +++ b/src/test/services/super-admin/OfficeFolderService.test.ts @@ -0,0 +1,286 @@ +import "module-alias/register"; +import "reflect-metadata"; +import { OfficeFolderHasCustomers, OfficeFolderHasStakeholders, PrismaClient } from "prisma/prisma-client"; +import { customer, customer_, deedType, documentType, documentType_, office, officeFolder, officeFolder_, user, user_ } from "@Test/config/MockedData"; +import Container from "typedi"; +import OfficeFoldersRepository from "@Repositories/OfficeFoldersRepository"; +import OfficeFolderService from "@Services/super-admin/OfficeFoldersService/OfficeFoldersService"; +import { initCustomers, initDeedType, initDocumentType, initOffice, initUsers } from "@Test/config/Init"; +import { OfficeFolder } from "le-coffre-resources/dist/SuperAdmin"; +import DeedTypesService from "@Services/super-admin/DeedTypesService/DeedTypesService"; + +const prisma = new PrismaClient(); + +const OfficeFolderServiceTest = new OfficeFolderService(Container.get(OfficeFoldersRepository), Container.get(DeedTypesService)); + +beforeAll(async () => { + office.uid = (await initOffice(office)).uid; + documentType.uid = (await initDocumentType(documentType, office)).uid; + documentType_.uid = (await initDocumentType(documentType_, office)).uid; + deedType.uid = (await initDeedType(deedType, office, [documentType.uid])).uid; + user.uid = (await initUsers(user)).uid; + user_.uid = (await initUsers(user_)).uid; + customer.uid = (await initCustomers(customer)).uid; + customer_.uid = (await initCustomers(customer_)).uid; +}); + +afterAll(async () => { + /* + * Clean database after all tests execution. + * Due to cascade deletion, if addresses are deleted, all items following tables are dropped: contacts, customers, offices + */ + const deleteDeedTypes = prisma.deedTypes.deleteMany(); + const deleteAddresses = prisma.addresses.deleteMany(); + await prisma.$transaction([deleteDeedTypes, deleteAddresses]); + await prisma.$disconnect(); +}); + +describe("test create function", () => { + it("should not create a new office folder if deed type is unknown", async () => { + let officeFolderWithoutDeedTypeUid: OfficeFolder = JSON.parse(JSON.stringify(officeFolder)); + officeFolderWithoutDeedTypeUid.deed.deed_type.uid = "random uid"; + // try to create a new deed with unknown deed type + async function createOfficeFolderWithUnknownDeedType() { + await OfficeFolderServiceTest.create(officeFolderWithoutDeedTypeUid); + } + await expect(createOfficeFolderWithUnknownDeedType).rejects.toThrow(); + }); + + it("should not create a new office folder if deed type is archived", async () => { + let officeFolderWithArchivatedDeedType: OfficeFolder = JSON.parse(JSON.stringify(officeFolder)); + // try to create a new deed with unknown deed type + async function createOfficeFolderWithArchivedDeedType() { + await OfficeFolderServiceTest.create(officeFolderWithArchivatedDeedType); + } + await expect(createOfficeFolderWithArchivedDeedType).rejects.toThrow("deed type is archived"); + }); + + it("should create a new office folder based on existing deed type", async () => { + const officeFolderCreated = await OfficeFolderServiceTest.create(officeFolder); + + const deedCreated = await prisma.deeds.findUniqueOrThrow({ where: { uid: officeFolderCreated.deed_uid } }); + + expect(officeFolderCreated.name).toEqual(officeFolder.name); + expect(officeFolderCreated.folder_number).toEqual(officeFolder.folder_number); + expect(officeFolderCreated.description).toEqual(officeFolder.description); + expect(officeFolderCreated.archived_description).toEqual(null); + expect(officeFolderCreated.status).toEqual("LIVE"); + expect(officeFolderCreated.office_uid).toEqual(officeFolder.office.uid); + expect(deedCreated.deed_type_uid).toEqual(officeFolder.deed.deed_type.uid); + }); + + it("should contains stakeholders", async () => { + const officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({ + include: { office_folder_has_stakeholder: true }, + }); + const stakeholderRelation = await prisma.officeFolderHasStakeholders.findUniqueOrThrow({ + where: { + office_folder_uid_user_stakeholder_uid: { + user_stakeholder_uid: user.uid!, + office_folder_uid: officeFolderCreated.uid, + }, + }, + }); + const stakeholderRelation_ = await prisma.officeFolderHasStakeholders.findUniqueOrThrow({ + where: { + office_folder_uid_user_stakeholder_uid: { + user_stakeholder_uid: user_.uid!, + office_folder_uid: officeFolderCreated.uid, + }, + }, + }); + expect(officeFolderCreated.office_folder_has_stakeholder.length).toEqual(2); + const stakeholder: OfficeFolderHasStakeholders = { + uid: stakeholderRelation.uid, + office_folder_uid: officeFolderCreated.uid, + user_stakeholder_uid: user.uid!, + created_at: officeFolderCreated.created_at, + updated_at: officeFolderCreated.updated_at, + }; + const stakeholder_: OfficeFolderHasStakeholders = { + uid: stakeholderRelation_.uid, + office_folder_uid: officeFolderCreated.uid, + user_stakeholder_uid: user_.uid!, + created_at: officeFolderCreated.created_at, + updated_at: officeFolderCreated.updated_at, + }; + expect(officeFolderCreated.office_folder_has_stakeholder).toEqual(expect.arrayContaining([stakeholder, stakeholder_])); + }); + + it("should not create a new office folder with folder number already created", async () => { + let officeFolderWithSameFolderNumber: OfficeFolder = JSON.parse(JSON.stringify(officeFolder_)); + officeFolderWithSameFolderNumber.folder_number = officeFolder.folder_number; + // try to create a new deed with unknown deed type + async function createOfficeFolderWithSameFolderNumber() { + await OfficeFolderServiceTest.create(officeFolderWithSameFolderNumber); + } + await expect(createOfficeFolderWithSameFolderNumber).rejects.toThrow(); + }); + + it("should not create a new office folder if deed type is archived", async () => { + await prisma.deedTypes.update({ where: { uid: deedType.uid }, data: { archived_at: new Date(Date.now()) } }); + // try to create a new deed with archivated deed type + async function createDeedWithArchivatedDeedType() { + await OfficeFolderServiceTest.create(officeFolder); + } + await expect(createDeedWithArchivatedDeedType).rejects.toThrow("deed type is archived"); + }); +}); + +describe("test update function", () => { + it("should add customers", async () => { + let officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({ + include: { office_folder_has_customers: true }, + }); + + expect(officeFolderCreated.office_folder_has_customers).toEqual([]); + // mocked data contains the customers + await OfficeFolderServiceTest.update(officeFolderCreated.uid, officeFolder); + + const customerRelation = await prisma.officeFolderHasCustomers.findUniqueOrThrow({ + where: { + office_folder_uid_customer_uid: { + customer_uid: customer.uid!, + office_folder_uid: officeFolderCreated.uid, + }, + }, + }); + const customerRelation_ = await prisma.officeFolderHasCustomers.findUniqueOrThrow({ + where: { + office_folder_uid_customer_uid: { + customer_uid: customer_.uid!, + office_folder_uid: officeFolderCreated.uid, + }, + }, + }); + + officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({ + include: { office_folder_has_customers: true }, + }); + + expect(officeFolderCreated.office_folder_has_customers.length).toEqual(2); + const officeFolderHasCustomer: OfficeFolderHasCustomers = { + uid: customerRelation.uid, + office_folder_uid: officeFolderCreated.uid, + customer_uid: customer.uid!, + created_at: customerRelation.created_at, + updated_at: customerRelation.updated_at, + }; + const officeFolderHasCustomer_: OfficeFolderHasCustomers = { + uid: customerRelation_.uid, + office_folder_uid: officeFolderCreated.uid, + customer_uid: customer_.uid!, + created_at: customerRelation_.created_at, + updated_at: customerRelation_.updated_at, + }; + expect(officeFolderCreated.office_folder_has_customers).toEqual( + expect.arrayContaining([officeFolderHasCustomer, officeFolderHasCustomer_]), + ); + }); + + it("should remove customers", async () => { + let officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({ + include: { office_folder_has_customers: true }, + }); + + expect(officeFolderCreated.office_folder_has_customers.length).toEqual(2); + + let officeFolderWithLessCustomers: OfficeFolder = JSON.parse(JSON.stringify(officeFolder)); + officeFolderWithLessCustomers.office_folder_has_customers!.pop(); + // mocked data contains the customers + await OfficeFolderServiceTest.update(officeFolderCreated.uid, officeFolderWithLessCustomers); + + const customerRelation = await prisma.officeFolderHasCustomers.findUniqueOrThrow({ + where: { + office_folder_uid_customer_uid: { + customer_uid: customer.uid!, + office_folder_uid: officeFolderCreated.uid, + }, + }, + }); + + officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({ + include: { office_folder_has_customers: true }, + }); + + expect(officeFolderCreated.office_folder_has_customers.length).toEqual(1); + const officeFolderHasCustomer: OfficeFolderHasCustomers = { + uid: customerRelation.uid, + office_folder_uid: officeFolderCreated.uid, + customer_uid: customer.uid!, + created_at: customerRelation.created_at, + updated_at: customerRelation.updated_at, + }; + expect(officeFolderCreated.office_folder_has_customers).toEqual([officeFolderHasCustomer]); + }); + it("should remove stakeholders", async () => { + let officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({ + include: { office_folder_has_stakeholder: true }, + }); + + expect(officeFolderCreated.office_folder_has_stakeholder.length).toEqual(2); + + let officeFolderWithLessStakeholders: OfficeFolder = JSON.parse(JSON.stringify(officeFolder)); + officeFolderWithLessStakeholders.office_folder_has_stakeholder!.pop(); + // mocked data contains the customers + await OfficeFolderServiceTest.update(officeFolderCreated.uid, officeFolderWithLessStakeholders); + + const stakeholderRelation = await prisma.officeFolderHasStakeholders.findUniqueOrThrow({ + where: { + office_folder_uid_user_stakeholder_uid: { + user_stakeholder_uid: user.uid!, + office_folder_uid: officeFolderCreated.uid, + }, + }, + }); + + officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({ + include: { office_folder_has_stakeholder: true }, + }); + + expect(officeFolderCreated.office_folder_has_stakeholder.length).toEqual(1); + const officeFolderHasStakeholder: OfficeFolderHasStakeholders = { + uid: stakeholderRelation.uid, + office_folder_uid: officeFolderCreated.uid, + user_stakeholder_uid: user.uid!, + created_at: stakeholderRelation.created_at, + updated_at: stakeholderRelation.updated_at, + }; + expect(officeFolderCreated.office_folder_has_stakeholder).toEqual([officeFolderHasStakeholder]); + }); + it("should archivate an office folder", async () => { + const officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({}); + let officeFolderArchived: OfficeFolder = JSON.parse(JSON.stringify(officeFolder)); + // mocked data for this office folder is "ARCHIVATED" by default + officeFolderArchived.archived_description = "folder complete"; + + const officeFolderUpdated = await OfficeFolderServiceTest.update(officeFolderCreated.uid, officeFolderArchived); + + expect(officeFolderUpdated.archived_description).toEqual(officeFolderArchived.archived_description); + expect(officeFolderUpdated.status).toEqual("ARCHIVED"); + }); + it("should unarchivate an office folder", async () => { + const officeFolderCreated = await prisma.officeFolders.findFirstOrThrow({}); + + expect(officeFolderCreated.archived_description).toEqual("folder complete"); + expect(officeFolderCreated.status).toEqual("ARCHIVED"); + + let officeFolderUnarchived: OfficeFolder = JSON.parse(JSON.stringify(officeFolder)); + officeFolderUnarchived.status = "LIVE"; + + const officeFolderUpdated = await OfficeFolderServiceTest.update(officeFolderCreated.uid, officeFolderUnarchived); + + expect(officeFolderUpdated.archived_description).toBeNull(); + expect(officeFolderUpdated.status).toEqual("LIVE"); + }); +}); + +describe("test get function", () => { + it("should return an array of officeFolders", async () => { + const officeFolders = await OfficeFolderServiceTest.get({}); + + // verify result typing + expect(officeFolders).toBeInstanceOf(Array); + expect(officeFolders.length).toEqual(1); + }); +}); diff --git a/src/test/services/super-admin/UsersService.test.ts b/src/test/services/super-admin/UsersService.test.ts new file mode 100644 index 00000000..a4ce6f06 --- /dev/null +++ b/src/test/services/super-admin/UsersService.test.ts @@ -0,0 +1,225 @@ +import "module-alias/register"; +import "reflect-metadata"; +import User from "le-coffre-resources/dist/SuperAdmin"; +import UsersService from "@Services/super-admin/UsersService/UsersService"; +import { PrismaClient } from "@prisma/client"; +import { user, userContact, userContact_, user_ } from "@Test/config/MockedData"; +import UsersRepository from "@Repositories/UsersRepository"; +import Container from "typedi"; + +const prisma = new PrismaClient(); + +const UsersServiceTest = new UsersService(Container.get(UsersRepository)); + +afterAll(async () => { + /* + * Clean database after all tests execution. + * Due to cascade deletion, if addresses are deleted, all items following tables are dropped: contacts, users, offices + */ + const deleteAddresses = prisma.addresses.deleteMany(); + await prisma.$transaction([deleteAddresses]); + await prisma.$disconnect(); +}); + +describe("test create function", () => { + it("should create a new user", async () => { + const userCreated = await UsersServiceTest.create(user); + + expect(userCreated?.idNot).toEqual(user.idNot); + + // verify if user contact is created in db + const contactCreated = await prisma.contacts.findUnique({ where: { uid: userCreated.contact_uid } }); + expect(contactCreated?.first_name).toEqual(user.contact.first_name); + expect(contactCreated?.last_name).toEqual(user.contact.last_name); + expect(contactCreated?.cell_phone_number).toEqual(user.contact.cell_phone_number); + expect(contactCreated?.phone_number).toEqual(user.contact.phone_number); + expect(contactCreated?.civility).toEqual(user.contact.civility); + expect(contactCreated?.email).toEqual(user.contact.email); + + // verify if user address is created in db + const addressForContactCreated = await prisma.addresses.findUnique({ where: { uid: contactCreated?.address_uid } }); + expect(addressForContactCreated?.address).toEqual(user.contact.address?.address); + expect(addressForContactCreated?.zip_code).toEqual(user.contact.address?.zip_code); + expect(addressForContactCreated?.city).toEqual(user.contact.address?.city); + + // verify if user office is created in db + const officeCreated = await prisma.offices.findUnique({ where: { uid: userCreated.office_uid } }); + expect(officeCreated?.idNot).toEqual(user.office_membership.idNot); + expect(officeCreated?.name).toEqual(user.office_membership.name); + expect(officeCreated?.crpcen).toEqual(user.office_membership.crpcen); + expect(officeCreated?.office_status).toEqual("DESACTIVATED"); + + // verify if user office's address is created in db + const addressForOfficeCreated = await prisma.addresses.findUnique({ where: { uid: officeCreated?.address_uid } }); + expect(addressForOfficeCreated?.address).toEqual(user.office_membership.address.address); + expect(addressForOfficeCreated?.zip_code).toEqual(user.office_membership.address.zip_code); + expect(addressForOfficeCreated?.city).toEqual(user.office_membership.address.city); + }); + + it("should not create an user already created", async () => { + // try to create the same user + async function duplicateUser() { + await UsersServiceTest.create(user); + } + await expect(duplicateUser).rejects.toThrow(); + }); + + it("should not create an new user with an email already created", async () => { + let newUser: User = JSON.parse(JSON.stringify(user_)); + newUser.contact.email = userContact.email; + + // try to create a new user with already used email + async function createUserWithDuplicateEmail() { + await UsersServiceTest.create(newUser); + } + await expect(createUserWithDuplicateEmail).rejects.toThrow(); + }); + + it("should not create an user with an phone number already created", async () => { + let newUser: User = JSON.parse(JSON.stringify(user_)); + newUser.contact.cell_phone_number = userContact.cell_phone_number; + + // try to create a new user with already used cellphone number + async function duplicateUser() { + await UsersServiceTest.create(newUser); + } + await expect(duplicateUser).rejects.toThrow(); + }); + + it("should create an new user if unique attributes differ from existing users", async () => { + let newUser: User = JSON.parse(JSON.stringify(user)); + newUser.idNot = user_.idNot; + newUser.contact.email = userContact_.email; + newUser.contact.cell_phone_number = userContact_.cell_phone_number; + + const userCreated = await UsersServiceTest.create(newUser); + + expect(userCreated?.idNot).toEqual(user_.idNot); + + // verify if user_ contact is created in db + const contactCreated = await prisma.contacts.findUnique({ where: { uid: userCreated.contact_uid } }); + expect(contactCreated?.first_name).toEqual(user.contact.first_name); + expect(contactCreated?.last_name).toEqual(user.contact.last_name); + expect(contactCreated?.cell_phone_number).toEqual(user_.contact.cell_phone_number); + expect(contactCreated?.phone_number).toEqual(user.contact.phone_number); + expect(contactCreated?.civility).toEqual(user.contact.civility); + expect(contactCreated?.email).toEqual(user_.contact.email); + + // verify if user_ address is created in db + const addressForContactCreated = await prisma.addresses.findUnique({ where: { uid: contactCreated?.address_uid } }); + expect(addressForContactCreated?.address).toEqual(user.contact.address?.address); + expect(addressForContactCreated?.zip_code).toEqual(user.contact.address?.zip_code); + expect(addressForContactCreated?.city).toEqual(user.contact.address?.city); + + // verify if user joined the existing office + const officeJoined = await prisma.offices.findUnique({ where: { uid: userCreated.office_uid } }); + expect(officeJoined?.idNot).toEqual(user.office_membership.idNot); + expect(officeJoined?.name).toEqual(user.office_membership.name); + expect(officeJoined?.crpcen).toEqual(user.office_membership.crpcen); + expect(officeJoined?.office_status).toEqual("DESACTIVATED"); + }); +}); + +describe("test update function", () => { + it("should update an user's data", async () => { + const userCreated = await prisma.users.findFirstOrThrow({ where: { idNot: user_.idNot } }); + + const officeJoined = await prisma.offices.findUnique({ where: { uid: userCreated.office_uid } }); + expect(officeJoined?.idNot).toEqual(user.office_membership.idNot); + expect(officeJoined?.name).toEqual(user.office_membership.name); + expect(officeJoined?.crpcen).toEqual(user.office_membership.crpcen); + expect(officeJoined?.office_status).toEqual("DESACTIVATED"); + + // update the last user created with his own new office and own contact + const updatedUser = await UsersServiceTest.update(userCreated.uid, user_); + + expect(updatedUser?.idNot).toEqual(user_.idNot); + + // verify if user_ contact is created in db + const existingContact = await prisma.contacts.findUnique({ where: { uid: updatedUser.contact_uid } }); + expect(existingContact?.first_name).toEqual(user_.contact.first_name); + expect(existingContact?.last_name).toEqual(user_.contact.last_name); + expect(existingContact?.cell_phone_number).toEqual(user_.contact.cell_phone_number); + expect(existingContact?.phone_number).toEqual(user_.contact.phone_number); + expect(existingContact?.civility).toEqual(user_.contact.civility); + expect(existingContact?.email).toEqual(user_.contact.email); + + // verify if user_ address is created in db + const addressForExistingContact = await prisma.addresses.findUnique({ where: { uid: existingContact?.address_uid } }); + expect(addressForExistingContact?.address).toEqual(user_.contact.address?.address); + expect(addressForExistingContact?.zip_code).toEqual(user_.contact.address?.zip_code); + expect(addressForExistingContact?.city).toEqual(user_.contact.address?.city); + + // verify if user_ joined the new office + const officeCreated = await prisma.offices.findUnique({ where: { uid: updatedUser.office_uid } }); + expect(officeCreated?.idNot).toEqual(user_.office_membership.idNot); + expect(officeCreated?.name).toEqual(user_.office_membership.name); + expect(officeCreated?.crpcen).toEqual(user_.office_membership.crpcen); + expect(officeCreated?.office_status).toEqual("DESACTIVATED"); + + // verify is user_ office's address is created in db + const addressForOfficeCreated = await prisma.addresses.findUnique({ where: { uid: officeCreated?.address_uid } }); + expect(addressForOfficeCreated?.address).toEqual(user_.office_membership.address.address); + expect(addressForOfficeCreated?.zip_code).toEqual(user_.office_membership.address.zip_code); + expect(addressForOfficeCreated?.city).toEqual(user_.office_membership.address.city); + }); + + it("should not update an user with an email already used", async () => { + const userUid = (await prisma.users.findFirstOrThrow({ where: { idNot: user_.idNot } })).uid; + let updatedUser: User = JSON.parse(JSON.stringify(user_)); + updatedUser.contact.email = userContact.email; + + // try to create a new user with already used email + async function updateUserWithDuplicateEmail() { + await UsersServiceTest.update(userUid, updatedUser); + } + await expect(updateUserWithDuplicateEmail).rejects.toThrow(); + }); + + it("should not update an user with an phone number already used", async () => { + const userUid = (await prisma.users.findFirstOrThrow({ where: { idNot: user_.idNot } })).uid; + let updatedUser: User = JSON.parse(JSON.stringify(user_)); + updatedUser.contact.cell_phone_number = userContact.cell_phone_number; + + // try to create a new user with already used email + async function updateUserWithDuplicateEmail() { + await UsersServiceTest.update(userUid, updatedUser); + } + await expect(updateUserWithDuplicateEmail).rejects.toThrow(); + }); +}); + +describe("test get function", () => { + it("should return an array of Users", async () => { + const req = {}; + const users = await UsersServiceTest.get(req); + + // verify result typing + expect(users).toBeInstanceOf(Array); + expect(users.length).toEqual(2); + + // verify result content + const usersCreated = await prisma.users.findMany(); + expect(users).toContainEqual(usersCreated[0]); + expect(users).toContainEqual(usersCreated[1]); + }); + + it("should return an array of Users per offices", async () => { + const officesCreated = await prisma.offices.findMany(); + const reqForFirstOffice = { where: { office_uid: officesCreated[0]?.uid } }; + const usersForFirstOffice = await UsersServiceTest.get(reqForFirstOffice); + + expect(usersForFirstOffice.length).toEqual(1); + + // verify result content + expect(usersForFirstOffice[0]?.idNot).toEqual(user.idNot); + + const reqForSecondOffice = { where: { office_uid: officesCreated[1]?.uid } }; + const usersForSecondOffice = await UsersServiceTest.get(reqForSecondOffice); + + expect(usersForSecondOffice.length).toEqual(1); + + // verify result content + expect(usersForSecondOffice[0]?.idNot).toEqual(user_.idNot); + }); +}); diff --git a/temp.yaml b/temp.yaml new file mode 100644 index 00000000..a34b777c --- /dev/null +++ b/temp.yaml @@ -0,0 +1,124 @@ +--- +# Source: leCoffre-back/templates/service-account.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: lecoffre-back-sa +--- +# Source: leCoffre-back/templates/service-account.yaml +apiVersion: v1 +kind: Secret +metadata: + name: lecoffre-back-sa-token + annotations: + kubernetes.io/service-account.name: lecoffre-back-sa +type: kubernetes.io/service-account-token +--- +# Source: leCoffre-back/templates/lecoffre-back.yaml +apiVersion: v1 +kind: Service +metadata: + name: lecoffre-back-svc + namespace: lecoffre + labels: +spec: + ports: + - port: 80 + name: http + targetPort: 1337 + selector: + app: lecoffre-back +--- +# Source: leCoffre-back/templates/lecoffre-back.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lecoffre-back + namespace: lecoffre + labels: + app: lecoffre-back +spec: + replicas: 1 + selector: + matchLabels: + app: lecoffre-back + template: + metadata: + annotations: + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/agent-inject-secret-envs-api: secret/data/lecoffre-back-stg/config/envs-api + vault.hashicorp.com/agent-inject-template-envs-api: | + {{ with secret "secret/data/lecoffre-back-stg/config/envs-api" }} + {{ range $k, $v := .Data.data }} + export {{ $k }}="{{ $v }}" + {{ end }} + {{ end }} + vault.hashicorp.com/agent-pre-populate-only: "true" + vault.hashicorp.com/role: custom_lecoffre-back_injector_rol + labels: + app: lecoffre-back + spec: + serviceAccountName: lecoffre-back-sa + imagePullSecrets: + - name: docker-pull-secret + containers: + - name: lecoffre-back + image: "rg.fr-par.scw.cloud/lecoffre/back:v0.3.2" + + resources: + limits: + memory: 2Gi + requests: + cpu: 200m + memory: 1Gi + + imagePullPolicy: Always + command: ['sh', '-c', '. /vault/secrets/envs-api && npm start'] +--- +# Source: leCoffre-back/templates/lecoffre-back.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: lecoffre-back + namespace: lecoffre + + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/from-to-www-redirect: "true" + +spec: + + rules: + - host: api.stg.lecoffre.smart-chain.fr + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: lecoffre-back-svc + port: + number: 80 +--- +# Source: leCoffre-back/templates/docker-pull-secret.yaml +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: docker-pull-secret +spec: + refreshInterval: 1h + secretStoreRef: + name: dockerpullsecret-vault-cluster-secret-store + kind: ClusterSecretStore + target: + template: + type: kubernetes.io/dockerconfigjson + name: docker-pull-secret + creationPolicy: Owner + data: + - secretKey: .dockerconfigjson + remoteRef: + key: secret/data/minteed-stg/config/dockerpullsecret + property: .dockerconfigjson diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..b0dd3f39 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,103 @@ +{ + "compilerOptions": { + "incremental": false, + // "module": "es2022", + "target": "es2017", + "module": "commonjs", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "jsx": "preserve", + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "noEmit": false, + "importHelpers": true, + "resolveJsonModule": true, + /* Strict Type-Checking Options */ + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "exactOptionalPropertyTypes": false, + "noImplicitOverride": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noPropertyAccessFromIndexSignature": true, + /* Additional Checks */ + "noUnusedLocals": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + /* Module Resolution Options */ + "moduleResolution": "node", + "baseUrl": ".", + "paths": { + "@App/*": [ + "src/app/*" + ], + "@Api/*": [ + "src/app/api/*" + ], + "@Services/*": [ + "src/services/*" + ], + "@Repositories/*": [ + "src/common/repositories/*" + ], + "@Entries/*": [ + "src/entries/*" + ], + "@Common/*": [ + "src/common/*" + ], + "@Config/*": [ + "src/common/config/*" + ], + "@Entities/*": [ + "src/common/ressources/*" + ], + "@System/*": [ + "src/common/system/*" + ], + "@ControllerPattern/*": [ + "src/common/system/controller-pattern/*" + ], + "@Test/*":[ + "src/test/*" + ], + }, + // "rootDirs": [], + // "typeRoots": [], + // "types": [], + // "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + // "allowUmdGlobalAccess": true, + /* Source Map Options */ + //"sourceRoot": "./src", + //"mapRoot": "./dist", + //"inlineSourceMap": false, + //"inlineSources": false, + /* Experimental Options */ + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "isolatedModules": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "src/app/api/admin/UsersController.ts" +, "src/common/databases/seeders/seeder.ts" ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file